Repository: Technion-Kishony-lab/quibbler (language: python)

Issue:
quibs persist despite being deleted
```python
a = iquib(1)
b = a + 2
ref_a = ref(a)
ref_b = ref(b)
del a, b

assert ref_a() is None
assert ref_b() is None
```




Discussion / hints:

Fix patch:
diff --git a/debuging_tools/deep_get_referrers.py b/debuging_tools/deep_get_referrers.py
new file mode 100644
index 000000000..5f1bfcc78
--- /dev/null
+++ b/debuging_tools/deep_get_referrers.py
@@ -0,0 +1,13 @@
+import gc
+from typing import Optional
+
+
+def deep_get_referrers(obj, depth: int, number_sequence=None):
+    if depth<0:
+        return
+    number_sequence = number_sequence or []
+    refs = gc.get_referrers(obj)
+    for num, ref in enumerate(refs):
+        current_number_sequence = [*number_sequence, num]
+        print(f'SEQ={current_number_sequence}: TYPE={type(ref)}', ref, end='\n\n')
+        deep_get_referrers(ref, depth-1, current_number_sequence)
diff --git a/other/list_quibbler_methods.py b/debuging_tools/list_quibbler_methods.py
similarity index 100%
rename from other/list_quibbler_methods.py
rename to debuging_tools/list_quibbler_methods.py
diff --git a/examples/notebooks/quibdemo_compare_images.ipynb b/examples/notebooks/quibdemo_compare_images.ipynb
index b31b9f9b7..bbe1d110a 100644
--- a/examples/notebooks/quibdemo_compare_images.ipynb
+++ b/examples/notebooks/quibdemo_compare_images.ipynb
@@ -6,9 +6,10 @@
    "metadata": {},
    "source": [
     "## Compare images\n",
-    "**Pairwise image comparison demosntrating diverged analysis.**\n",
     "\n",
-    "This analysis cuts _n_ sub-images from a a source image based on user specified Region of Interests (ROIs). Performs all pair-wise comparisons and cluster images based on color similarity. When a single ROI changes (is being dragged), quibbler knows to only make the calculation needed to cut this specific image (see a single reporting of: \"Cutting image\") and re-performs the pairwise comparisons of this image with all others (2 _n_ - 1 recalculations. See reporting of\"Comparing images\"). \n",
+    "**Pairwise image comparison demonstrating diverged analysis.**\n",
+    "\n",
+    "This analysis cuts _n_ sub-images from a source image based on user specified Region of Interests (ROIs), then performs all pair-wise color-comparisons among these cut images and cluster the images based on color similarity. When a single ROI changes (is being dragged), *Quibbler* knows to only make the calculation needed: cutting this specific image (see a single reporting of: \"Cutting image\") and re-performing the pairwise comparisons of this image with all others (2 _n_ - 1 recalculations. See reporting of \"Comparing images\"). \n",
     "\n",
     "* **Features**\n",
     "    * Diverged calculations of quib slices\n",
@@ -19,37 +20,43 @@
     "\n",
     "\n",
     "* **Try me**\n",
-    "    * Drag the cyan diamond marker to choose a squared number.\n",
-    "    * Drag the cyan square corner."
+    "    * Drag each region of interest to define the cut images. Watch the number of re-calculations being made.\n",
+    "    * Change the similarity threshold either with the slider or on the color-scale. \n",
+    "    * Add or remove images with the \"Image count\" slider.\n"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": null,
+   "execution_count": 1,
    "id": "3d35ab5e",
    "metadata": {},
    "outputs": [],
    "source": [
+    "# Imports:\n",
+    "\n",
+    "import pyquibbler as qb\n",
+    "from pyquibbler import iquib, q, quiby\n",
+    "qb.override_all()\n",
+    "\n",
     "import numpy as np\n",
+    "from scipy.sparse.csgraph import connected_components\n",
     "from functools import partial\n",
     "from matplotlib import pyplot as plt, widgets\n",
     "from mpl_toolkits.axes_grid1 import ImageGrid\n",
     "\n",
-    "from pyquibbler import iquib, override_all, q\n",
-    "from scipy.sparse.csgraph import connected_components\n",
-    "override_all()\n",
-    "\n",
     "%matplotlib tk"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": null,
+   "execution_count": 2,
    "id": "6665ec4a",
    "metadata": {},
    "outputs": [],
    "source": [
-    "@partial(np.vectorize, signature='(4),()->()', pass_quibs=True, lazy=False)\n",
+    "# Define vectorized analysis steps:\n",
+    "\n",
+    "@partial(np.vectorize, signature='(4),()->()', pass_quibs=True, is_graphics=True)\n",
     "def create_roi(roi, axs):\n",
     "    rectprops = dict(facecolor='k', edgecolor='k', alpha=0.2, fill=True)\n",
     "    widgets.RectangleSelector(axs, extents=roi, rectprops=rectprops)\n",
@@ -64,23 +71,21 @@
     "@partial(np.vectorize, otypes=[float])\n",
     "def image_distance(img1, img2):\n",
     "    print(\"Comparing images\")\n",
-    "    averages = np.average(img1, axis=(0, 1)) - np.average(img2, axis=(0, 1))\n",
-    "    return np.linalg.norm(averages) / np.linalg.norm(np.full(averages.shape, 255))\n",
+    "    rgd_distance = np.average(img1, axis=(0, 1)) - np.average(img2, axis=(0, 1))\n",
+    "    return np.linalg.norm(rgd_distance / 255)\n",
     "\n",
     "\n",
-    "@partial(np.vectorize,  signature='(),(4),()->()', lazy=False, otypes=[type(None)])\n",
-    "def plot_roi_label(axs, roi, index):\n",
-    "    axs.text(roi[1], roi[2], chr(index+65), fontsize=20)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "05524bcc",
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "@partial(np.vectorize, lazy=False)\n",
+    "@partial(np.vectorize, signature='(),()->()', is_graphics=True)\n",
+    "def show_cut_images(ax, img):\n",
+    "    ax.imshow(img)\n",
+    "\n",
+    "\n",
+    "@partial(np.vectorize, signature='(),()->()', is_graphics=True)\n",
+    "def plot_image_cluster_label(ax, index):\n",
+    "    ax.text(0, 0, chr(index+65), fontsize=20, ha='left', va='top')\n",
+    "\n",
+    "    \n",
+    "@partial(np.vectorize, is_graphics=True)\n",
     "def show_adjacency(axs, x, y, adjacent):\n",
     "    symbol = 'x' if adjacent else '.'\n",
     "    axs.plot(x, y, symbol, color='r')"
@@ -88,7 +93,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": null,
+   "execution_count": 3,
    "id": "a827bd69",
    "metadata": {},
    "outputs": [],
@@ -97,163 +102,407 @@
     "file_name = iquib('../data_files/pipes.jpg')\n",
     "image = plt.imread(file_name)\n",
     "\n",
-    "plt.figure(1, figsize=[10, 7])\n",
-    "plt.imshow(image)\n",
-    "ax1 = plt.gca()"
+    "plt.figure(1, figsize=[6, 6])\n",
+    "ax1 = plt.axes([0.15, 0.3, 0.7, 0.65])\n",
+    "ax1.imshow(image);"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": null,
+   "execution_count": 4,
    "id": "a7eb4a30",
    "metadata": {},
    "outputs": [],
    "source": [
-    "images_count = iquib(6)\n",
-    "images_count.set_assignment_template(0, 10, 1)\n",
+    "# Define ROIs:\n",
+    "images_count = iquib(6, assignment_template=(0, 10, 1))\n",
     "\n",
-    "roi_default = iquib([[20, 100, 20, 100]])\n",
-    "roi_default.allow_overriding = False\n",
+    "roi_default = iquib([[20, 100, 20, 100]], allow_overriding=False)\n",
     "\n",
     "rois = np.repeat(roi_default, images_count, axis=0)\n",
-    "rois.set_assignment_template(0, 1000, 1)\n",
-    "rois.allow_overriding = True\n",
+    "rois.setp(assignment_template=(0, 1000, 1), allow_overriding=True)\n",
     "\n",
-    "similiarity_threshold = iquib(np.array([.1]))"
+    "similiarity_threshold = iquib(.1)"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": null,
+   "execution_count": 5,
    "id": "2ee57972",
    "metadata": {},
    "outputs": [],
    "source": [
+    "# Cut the images from image according to the rois\n",
     "cut_images = cut_image(image, rois)"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": null,
-   "id": "951ed0ee",
-   "metadata": {},
-   "outputs": [],
-   "source": []
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
+   "execution_count": 6,
    "id": "804f77f8",
    "metadata": {},
    "outputs": [],
    "source": [
-    "create_roi(rois, ax1)"
+    "# Draw the rois\n",
+    "create_roi(rois, ax1);"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": null,
+   "execution_count": 7,
    "id": "cbad8f33",
    "metadata": {},
    "outputs": [],
    "source": [
+    "# Add slides for similarity threshold and image count:\n",
     "widgets.Slider(\n",
-    "    ax=plt.axes([0.25, 0.1, 0.65, 0.03]),\n",
-    "    label=q(\"Similiarity threshold {:.1f}\".format, similiarity_threshold[0]),\n",
+    "    ax=plt.axes([0.4, 0.2, 0.4, 0.03]),\n",
+    "    label=\"Similiarity threshold\",\n",
     "    valmin=0, valmax=1, valstep=.05,\n",
-    "    valinit=similiarity_threshold[0])\n",
+    "    valinit=similiarity_threshold)\n",
     "\n",
     "widgets.Slider(\n",
-    "    ax=plt.axes([0.25, 0.05, 0.65, 0.03]),\n",
-    "    label=q(\"Image count \".format, images_count),\n",
+    "    ax=plt.axes([0.4, 0.1, 0.4, 0.03]),\n",
+    "    label=\"Image count\",\n",
     "    valmin=1, valmax=9, valstep=1,\n",
     "    valinit=images_count);"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": null,
+   "execution_count": 8,
    "id": "7495e47f",
    "metadata": {},
-   "outputs": [],
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Cutting image\n",
+      "Cutting image\n",
+      "Cutting image\n",
+      "Cutting image\n",
+      "Cutting image\n",
+      "Cutting image\n"
+     ]
+    }
+   ],
    "source": [
-    "# Figure 2 - Plot images\n",
+    "# Figure 2 - Plot the cut images\n",
     "fig = plt.figure(2)\n",
-    "grid_axes = iquib(ImageGrid(fig, 111, nrows_ncols=(3, 3), axes_pad=0.1))"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "id": "e6897452",
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "np.vectorize(lambda ax, im: ax.imshow(im), signature='(),()->()', lazy=False)(\n",
-    "    grid_axes[:images_count], cut_images);"
+    "grid_axes = iquib(ImageGrid(fig, 111, nrows_ncols=(3, 3), axes_pad=0.1))\n",
+    "show_cut_images(grid_axes[:images_count], cut_images);"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": null,
+   "execution_count": 9,
    "id": "7735e890",
    "metadata": {},
    "outputs": [],
    "source": [
-    "# Figure 3 - Compare sub images\n",
+    "# Calculate all pairwise image comparisons \n",
     "image_distances = image_distance(np.expand_dims(cut_images, 1), cut_images)\n",
     "adjacents = image_distances < similiarity_threshold"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": null,
-   "id": "8ce6d47d",
+   "execution_count": 10,
+   "id": "9851b7f4-2a5c-4768-98b0-c3fc75f4de4a",
    "metadata": {},
-   "outputs": [],
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n"
+     ]
+    }
+   ],
    "source": [
-    "# Plot distance matrix\n",
+    "# Figure 3 - Plot distance matrix\n",
     "fig = plt.figure(3)\n",
     "fig.clf()\n",
     "axs = fig.add_axes([0.1, 0.15, 0.7, 0.7])\n",
-    "axs.imshow(1 - image_distances, cmap='gray', vmin=0, vmax=1)\n",
-    "axs.axis([-0.5, images_count - 0.5, -0.5, images_count - 0.5])\n",
+    "axs.imshow(1 - image_distances, cmap='gray', vmin=0, vmax=1) \\\n",
+    "    .setp(graphics_update='drop')\n",
+    "axs.axis([-0.5, images_count-0.5, -0.5, images_count-0.5])\n",
     "axs.set_title('pairwise distance between images')\n",
     "axs.set_xlabel('Image number')\n",
     "axs.set_ylabel('Image number')\n",
-    "show_adjacency(axs, np.expand_dims(np.arange(images_count), 1), np.arange(images_count), adjacents);\n",
+    "\n",
+    "image_nums = np.arange(images_count)\n",
+    "show_adjacency(axs, np.expand_dims(image_nums, 1), image_nums, adjacents) \\\n",
+    "    .setp(graphics_update='drop');\n",
     "\n",
     "# colormap\n",
     "axclr = fig.add_axes([0.85, 0.15, 0.06, 0.7])\n",
-    "clrmap = np.linspace(1,0,10).reshape(10,1)\n",
+    "clrmap = np.linspace(1, 0, 10).reshape(10, 1)\n",
     "axclr.imshow(clrmap, cmap='gray', vmin=0, vmax=1)\n",
-    "axclr.plot([-0.5,0.5], similiarity_threshold*10-0.5+np.array([0,0]), '-r', linewidth=4, picker=True)\n",
+    "axclr.plot([-0.5, 0.5], similiarity_threshold * 10 - 0.5 + np.array([0, 0]), \n",
+    "           '-r', linewidth=4, picker=True)\n",
     "axclr.set_xticks([])\n",
     "axclr.set_yticks([])\n",
-    "axclr.set_ylabel('Similarity Threshold')"
+    "axclr.set_ylabel('Similarity Threshold');"
    ]
   },
   {
    "cell_type": "code",
    "execution_count": null,
-   "id": "2146fe99",
+   "id": "7f7ba770-0779-4c05-a4a7-59daaa93e9b0",
    "metadata": {},
    "outputs": [],
    "source": [
     "# add cluster label\n",
-    "rois.get_value()"
+    "cluster_indeces = q(connected_components, adjacents)[1]\n",
+    "#plot_image_cluster_label(grid_axes[:images_count], cluster_indeces) \\\n",
+    "#    .setp(graphics_update='drop');"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": null,
-   "id": "2602c16d",
+   "execution_count": 13,
+   "id": "2ccfef23-114b-4a6b-861e-d549c74ba85f",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from pyquibbler import Project"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 14,
+   "id": "df77d6ce-2ecd-4915-bbf6-5a66580a7b9e",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "P = Project.get_or_create()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 15,
+   "id": "ceae6fd5-f788-430a-90fd-b163f938756e",
    "metadata": {},
    "outputs": [],
    "source": [
-    "c = q(connected_components,adjacents)[1]\n",
-    "plot_roi_label(ax1, rois, c)"
+    "grq = [quib for quib in P.quibs if quib.is_graphics_quib]"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 16,
+   "id": "fd90440c-ac9e-48b4-a4b1-9ac4e17f7dbc",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "np.vectorize(show_adjacency)(<Axes:title={'center':'pairwise distance between images'}, xlabel='Image number', ylabel='Image number'>, expand_dims(image_nums, 1), image_nums, adjacents)\n",
+      "GraphicsUpdateType.DROP\n",
+      "\n",
+      "imshow(<Axes:title={'center':'pairwise distance between images'}, xlabel='Image number', ylabel='Image number'>, 1 - image_distances, cmap='gray', vmin=0, vmax=1)\n",
+      "GraphicsUpdateType.DROP\n",
+      "\n",
+      "np.vectorize(plot_image_cluster_label, (),()->())(grid_axes[:images_count], cluster_indeces)\n",
+      "GraphicsUpdateType.DROP\n",
+      "\n",
+      "imshow(<Axes:>, image)\n",
+      "None\n",
+      "\n",
+      "np.vectorize(show_cut_images, (),()->())(grid_axes[:images_count], cut_images)\n",
+      "None\n",
+      "\n",
+      "QSlider(ax=<Axes:>, label='Image count', valmin=1, valmax=9, valstep=1, valinit=images_count)\n",
+      "None\n",
+      "\n",
+      "plot(<Axes:ylabel='Similarity Threshold'>, [-0.5, 0.5], similiarity_threshold * 10 - 0.5 + array([0, 0]), '-r', linewidth=4, picker=True)\n",
+      "None\n",
+      "\n",
+      "set_xlim(<Axes:title={'center':'pairwise distance between images'}, xlabel='Image number', ylabel='Image number'>, -0.5, images_count - 0.5, emit=True, auto=False)\n",
+      "None\n",
+      "\n",
+      "QSlider(ax=<Axes:>, label='Similiarity threshold', valmin=0, valmax=1, valstep=0.05, valinit=similiarity_threshold)\n",
+      "None\n",
+      "\n",
+      "np.vectorize(create_roi, (4),()->())(rois, <Axes:>)\n",
+      "None\n",
+      "\n",
+      "set_ylim(<Axes:title={'center':'pairwise distance between images'}, xlabel='Image number', ylabel='Image number'>, -0.5, images_count - 0.5, emit=True, auto=False)\n",
+      "None\n",
+      "\n"
+     ]
+    }
+   ],
+   "source": [
+    "for g in grq:\n",
+    "    print(g)\n",
+    "    print(g.graphics_update)\n",
+    "    print('')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 16,
+   "id": "3d2fd6c4-d69e-4a7b-bbeb-f6fcb1bcba1a",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "0"
+      ]
+     },
+     "execution_count": 12,
+     "metadata": {},
+     "output_type": "execute_result"
+    },
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Cutting image\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Cutting image\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Cutting image\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Cutting image\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Cutting image\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Cutting image\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Cutting image\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n",
+      "Comparing images\n"
+     ]
+    }
+   ],
+   "source": [
+    "0"
    ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "e6c9527b-5ec7-49ca-b3d4-f02a22c04c42",
+   "metadata": {},
+   "outputs": [],
+   "source": []
   }
  ],
  "metadata": {
@@ -272,9 +521,9 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.9.7"
+   "version": "3.10.4"
   }
  },
  "nbformat": 4,
  "nbformat_minor": 5
-}
\ No newline at end of file
+}
diff --git a/other/kukname.py b/other/kukname.py
deleted file mode 100644
index 73cfe28d0..000000000
--- a/other/kukname.py
+++ /dev/null
@@ -1,23 +0,0 @@
-from pyquibbler import q, iquib, override_all
-override_all()
-import  matplotlib
-matplotlib.use('TkAgg')
-import matplotlib.pyplot as plt
-
-f1 = plt.figure().gca()
-f2 = plt.figure().gca()
-f3 = plt.figure().gca()
-
-def mygraph(x):
-    f1.plot(range(len(x)), x)
-    f2.loglog(range(len(x)), x)
-
-
-a = iquib([1,2,3,4])
-qb = q(mygraph, a)
-
-f3.plot([1,2,3,4],a,'o',picker=True)
-
-qb.get_value()
-
-plt.show()
diff --git a/pyquibbler/function_definitions/func_call.py b/pyquibbler/function_definitions/func_call.py
index 51dd021ff..1cb838f7d 100644
--- a/pyquibbler/function_definitions/func_call.py
+++ b/pyquibbler/function_definitions/func_call.py
@@ -3,14 +3,14 @@
 import functools
 from abc import abstractmethod, ABC
 from dataclasses import dataclass
-from typing import Tuple, Any, Mapping, Optional, Callable, List, TYPE_CHECKING, Type, ClassVar
+from typing import Tuple, Any, Mapping, Optional, Callable, List, TYPE_CHECKING, Type, ClassVar, Dict
 
 from .location import SourceLocation, create_source_location
 from .types import Argument
 from pyquibbler.quib.external_call_failed_exception_handling import \
     external_call_failed_exception_handling
 from pyquibbler.utilities.iterators import iter_args_and_names_in_function_call, \
-    get_paths_for_objects_of_type, iter_object_type_in_args
+    get_paths_for_objects_of_type
 
 if TYPE_CHECKING:
     from pyquibbler.function_definitions import FuncDefinition
@@ -30,7 +30,7 @@ class FuncArgsKwargs:
 
     func: Callable
     args: Tuple[Any, ...]
-    kwargs: Mapping[str, Any]
+    kwargs: Dict[str, Any]
     include_defaults: bool
 
     def get_args_values_by_name_and_position(self) -> Tuple[Mapping[str, Any], Tuple[Any, ...]]:
@@ -202,21 +202,25 @@ def transform_sources_in_args_kwargs(self,
 
         return new_args, new_kwargs
 
-    @functools.lru_cache()
-    def get_objects_of_type_in_args_kwargs(self, type_):
-        return list(iter_object_type_in_args(type_, self.args, self.kwargs))
-
     def get_data_source_argument_values(self) -> List[Any]:
         return [v for _, v in self.func_definition.get_data_source_arguments_with_values(self.func_args_kwargs)]
 
     @load_source_locations_before_running
-    @functools.lru_cache()
+    # @functools.lru_cache() - remove because it leads to quib persistence. TODO: alternative solution
     def get_data_sources(self):
         sources = set()
         for location in self.data_source_locations:
             sources.add(location.find_in_args_kwargs(self.args, self.kwargs))
         return sources
 
+    @load_source_locations_before_running
+    # @functools.lru_cache() - remove because it leads to quib persistence. TODO: alternative solution
+    def get_parameter_sources(self):
+        sources = set()
+        for location in self.parameter_source_locations:
+            sources.add(location.find_in_args_kwargs(self.args, self.kwargs))
+        return sources
+
     @abstractmethod
     def run(self, *args, **kwargs):
         pass
diff --git a/pyquibbler/function_definitions/func_definition.py b/pyquibbler/function_definitions/func_definition.py
index 73725b16c..ea59bb9d9 100644
--- a/pyquibbler/function_definitions/func_definition.py
+++ b/pyquibbler/function_definitions/func_definition.py
@@ -35,7 +35,7 @@ class FuncDefinition:
     is_graphics: Optional[bool] = False  # None for 'maybe'
     pass_quibs: bool = False
     lazy: Optional[bool] = None  # None for auto: LAZY for non-graphics, GRAPHICS_LAZY for is_graphics=True
-    replace_previous_quibs_on_artists: bool = field(repr=False, default=False)
+    is_artist_setter: bool = field(repr=False, default=False)
     inverters: List[Type[Inverter]] = field(repr=False, default_factory=list)
     backwards_path_translators: List[Type[BackwardsPathTranslator]] = field(repr=False, default_factory=list)
     forwards_path_translators: List[Type[ForwardsPathTranslator]] = field(repr=False, default_factory=list)
@@ -105,7 +105,7 @@ def _get_all_data_source_arguments(self, func_args_kwargs: FuncArgsKwargs):
 
         return all_data_source_arguments
 
-    @functools.lru_cache()
+    # @functools.lru_cache() - remove because it leads to quib persistence. TODO: alternative solution
     def get_data_source_arguments_with_values(self, func_args_kwargs: FuncArgsKwargs):
         return [
             (argument, func_args_kwargs[argument])
@@ -144,7 +144,7 @@ def create_func_definition(raw_data_source_arguments: List[RawArgument] = None,
                            is_graphics: Optional[bool] = False,
                            pass_quibs: bool = False,
                            lazy: Optional[bool] = None,
-                           replace_previous_quibs_on_artists: bool = False,
+                           is_artist_setter: bool = False,
                            inverters: List[Type[Inverter]] = None,
                            backwards_path_translators: List[Type[BackwardsPathTranslator]] = None,
                            forwards_path_translators: List[Type[ForwardsPathTranslator]] = None,
@@ -173,6 +173,6 @@ def create_func_definition(raw_data_source_arguments: List[RawArgument] = None,
         quib_function_call_cls=quib_function_call_cls,
         pass_quibs=pass_quibs,
         lazy=lazy,
-        replace_previous_quibs_on_artists=replace_previous_quibs_on_artists,
+        is_artist_setter=is_artist_setter,
         **kwargs
     )
diff --git a/pyquibbler/function_overriding/non_quib_overrides.py b/pyquibbler/function_overriding/non_quib_overrides.py
index 28c7dfc76..fe808656c 100644
--- a/pyquibbler/function_overriding/non_quib_overrides.py
+++ b/pyquibbler/function_overriding/non_quib_overrides.py
@@ -1,5 +1,11 @@
+import functools
+from typing import Callable, Type
+
 import matplotlib.widgets
+
+from pyquibbler.graphics.utils import TYPES_TO_ARTIST_ARRAY_NAMES
 from pyquibbler.graphics.widgets import QRadioButtons, QSlider, QRectangleSelector
+from pyquibbler.quib.graphics import artist_wrapper
 
 widget_class_names_to_quib_supporting_widget = {
     'RadioButtons': QRadioButtons,
@@ -11,3 +17,30 @@
 def switch_widgets_to_quib_supporting_widgets():
     for widget_class_name, quib_supporting_widget in widget_class_names_to_quib_supporting_widget.items():
         setattr(matplotlib.widgets, widget_class_name, quib_supporting_widget)
+
+
+def get_clear_axes_wrapper(func: Callable):
+
+    @functools.wraps(func)
+    def _wrapper(self, *args, **kwargs):
+        artist_wrapper.clear_all_quibs(self)
+        for array_name in TYPES_TO_ARTIST_ARRAY_NAMES.values():
+            if hasattr(self, array_name):
+                for artist in getattr(self, array_name):
+                    artist_wrapper.clear_all_quibs(artist)
+        return func(self, *args, **kwargs)
+
+    return _wrapper
+
+
+def wrap_method(cls: Type, method_name: str, get_wrapper: Callable):
+    func = getattr(cls, method_name)
+    setattr(cls, method_name, get_wrapper(func))
+
+
+def override_clear_axes():
+    from matplotlib.axis import Axis
+    wrap_method(Axis, 'clear', get_clear_axes_wrapper)
+
+    from matplotlib.axes._base import _AxesBase
+    wrap_method(_AxesBase, 'cla', get_clear_axes_wrapper)
diff --git a/pyquibbler/function_overriding/override_all.py b/pyquibbler/function_overriding/override_all.py
index c9cafa51c..6351e6515 100644
--- a/pyquibbler/function_overriding/override_all.py
+++ b/pyquibbler/function_overriding/override_all.py
@@ -2,7 +2,8 @@
 
 from pyquibbler.function_definitions.definitions import add_definition_for_function
 from pyquibbler.function_overriding.function_override import FuncOverride
-from pyquibbler.function_overriding.non_quib_overrides import switch_widgets_to_quib_supporting_widgets
+from pyquibbler.function_overriding.non_quib_overrides import switch_widgets_to_quib_supporting_widgets, \
+    override_clear_axes
 from pyquibbler.function_overriding.defintion_without_override.python_functions import \
     create_defintions_for_python_functions
 from pyquibbler.function_overriding.quib_overrides.operators.overrides import create_operator_overrides
@@ -31,6 +32,7 @@ def override_all():
                                               *create_numpy_overrides(),
                                               *create_quib_method_overrides()]
     switch_widgets_to_quib_supporting_widgets()
+    override_clear_axes()
     for func_override in function_overrides:
         maybe_create_quib = func_override.override()
         add_definition_for_function(
diff --git a/pyquibbler/function_overriding/third_party_overriding/general_helpers.py b/pyquibbler/function_overriding/third_party_overriding/general_helpers.py
index 1e216817b..74fd4bf72 100644
--- a/pyquibbler/function_overriding/third_party_overriding/general_helpers.py
+++ b/pyquibbler/function_overriding/third_party_overriding/general_helpers.py
@@ -15,7 +15,7 @@ def override_with_cls(override_cls,
                       quib_function_call_cls=None, is_file_loading=False, is_random=False,
                       pass_quibs: bool = False,
                       lazy: Optional[bool] = None,
-                      replace_previous_quibs_on_artists: bool = False,
+                      is_artist_setter: bool = False,
                       is_graphics: bool = False,
                       func: Optional[Callable] = None,
                       func_definition_cls: Optional[Type[FuncDefinition]] = None,
@@ -32,7 +32,7 @@ def override_with_cls(override_cls,
                                                is_graphics=is_graphics,
                                                pass_quibs=pass_quibs,
                                                lazy=lazy,
-                                               replace_previous_quibs_on_artists=replace_previous_quibs_on_artists,
+                                               is_artist_setter=is_artist_setter,
                                                inverters=inverters,
                                                backwards_path_translators=backwards_path_translators,
                                                forwards_path_translators=forwards_path_translators,
diff --git a/pyquibbler/function_overriding/third_party_overriding/graphics/helpers.py b/pyquibbler/function_overriding/third_party_overriding/graphics/helpers.py
index 58bf05f17..95d7a7bf6 100644
--- a/pyquibbler/function_overriding/third_party_overriding/graphics/helpers.py
+++ b/pyquibbler/function_overriding/third_party_overriding/graphics/helpers.py
@@ -7,6 +7,7 @@
 
 from pyquibbler.function_overriding.function_override import FuncOverride
 from pyquibbler.function_overriding.third_party_overriding.general_helpers import override_with_cls
+from pyquibbler.quib.graphics import artist_wrapper
 
 from pyquibbler.quib.graphics.event_handling import CanvasEventHandler
 
@@ -33,9 +34,10 @@ def _call_wrapped_func(func, args, kwargs) -> Any:
         result = func(*args, **kwargs)
 
         ax = args[0]
-        name = f'_quibbler_{func.__name__}'
-        if hasattr(ax, name):
-            delattr(ax, name)
+        name = func.__name__
+
+        artist_wrapper.set_setter_quib(ax, name, None)
+
         return result
 
 
@@ -64,10 +66,10 @@ def _call_wrapped_func(func, args, kwargs) -> Any:
 graphics_override = functools.partial(override_with_cls, GraphicsOverride, is_graphics=True)
 axes_override = functools.partial(graphics_override, Axes)
 
-replacing_axes_override = functools.partial(override_with_cls, AxesSetOverride, Axes, is_graphics=True,
-                                            replace_previous_quibs_on_artists=True)
+axes_setter_override = functools.partial(override_with_cls, AxesSetOverride, Axes, is_graphics=True,
+                                         is_artist_setter=True)
 
 widget_override = functools.partial(graphics_override, matplotlib.widgets)
 
 axes_lim_override = functools.partial(override_with_cls, AxesLimOverride,
-                                      Axes, is_graphics=True, replace_previous_quibs_on_artists=True)
+                                      Axes, is_graphics=True, is_artist_setter=True)
diff --git a/pyquibbler/function_overriding/third_party_overriding/graphics/overrides.py b/pyquibbler/function_overriding/third_party_overriding/graphics/overrides.py
index d1a0f744a..861576c12 100644
--- a/pyquibbler/function_overriding/third_party_overriding/graphics/overrides.py
+++ b/pyquibbler/function_overriding/third_party_overriding/graphics/overrides.py
@@ -3,7 +3,7 @@
 import matplotlib.image
 from pyquibbler.function_overriding.third_party_overriding.general_helpers import file_loading_override
 from pyquibbler.function_overriding.third_party_overriding.graphics.helpers import axes_override, \
-    replacing_axes_override, widget_override, axes_lim_override
+    axes_setter_override, widget_override, axes_lim_override
 from pyquibbler.quib.func_calling.func_calls import RadioButtonsQuibFuncCall, SliderQuibFuncCall, \
     RectangleSelectorQuibFuncCall,  CheckButtonsQuibFuncCall
 from pyquibbler.quib.func_calling.func_calls.known_graphics.plot_call import PlotQuibFuncCall
@@ -27,7 +27,7 @@ def create_graphics_overrides():
             'scatter',
         )),
 
-        *(replacing_axes_override(func_name) for func_name in (
+        *(axes_setter_override(func_name) for func_name in (
             'set_xticks',
             'set_yticks',
             'set_xticklabels',
diff --git a/pyquibbler/graphics/drag_context_manager.py b/pyquibbler/graphics/drag_context_manager.py
index dbc317e43..08282ef93 100644
--- a/pyquibbler/graphics/drag_context_manager.py
+++ b/pyquibbler/graphics/drag_context_manager.py
@@ -13,22 +13,40 @@ def is_within_drag():
     return WITHIN_DRAG_COUNT > 0 and not is_within_release()
 
 
-@contextlib.contextmanager
-def dragging():
+def enter_dragging():
     global WITHIN_DRAG_COUNT
     WITHIN_DRAG_COUNT += 1
-    yield
+
+
+def exit_dragging():
+    global WITHIN_DRAG_COUNT
     WITHIN_DRAG_COUNT -= 1
 
 
-@contextlib.contextmanager
-def releasing():
+def enter_releasing():
     global WITHIN_RELEASE_COUNT
     WITHIN_RELEASE_COUNT += 1
-    yield
+
+
+def exist_releasing():
+    global WITHIN_RELEASE_COUNT
     WITHIN_RELEASE_COUNT -= 1
 
 
+@contextlib.contextmanager
+def dragging():
+    enter_dragging()
+    yield
+    exit_dragging()
+
+
+@contextlib.contextmanager
+def releasing():
+    enter_releasing()
+    yield
+    exist_releasing()
+
+
 def pressed():
     global IS_PRESSED
     IS_PRESSED = True
diff --git a/pyquibbler/graphics/utils.py b/pyquibbler/graphics/utils.py
index db6c03045..a4aff3407 100644
--- a/pyquibbler/graphics/utils.py
+++ b/pyquibbler/graphics/utils.py
@@ -56,7 +56,11 @@ def remove_artist(artist: Artist):
     """
     Remove an artist from its artist array (does NOT redraw)
     """
-    get_artist_array(artist).remove(artist)
+    from pyquibbler.quib.graphics import artist_wrapper
+    artist_wrapper.clear_all_quibs(artist)
+    artist_array = get_artist_array(artist)
+    if artist in artist_array:  # artists not in artist array have been observed in the wild
+        artist_array.remove(artist)
 
 
 def get_axeses_to_array_names_to_starting_indices_and_artists(artists: List[Artist]) \
diff --git a/pyquibbler/graphics/widgets/q_slider.py b/pyquibbler/graphics/widgets/q_slider.py
index 2bf4874a5..c8d36bc4f 100644
--- a/pyquibbler/graphics/widgets/q_slider.py
+++ b/pyquibbler/graphics/widgets/q_slider.py
@@ -1,5 +1,7 @@
 from matplotlib.widgets import Slider
 
+from pyquibbler.graphics.drag_context_manager import enter_dragging, exit_dragging, releasing
+
 
 class QSlider(Slider):
     def __init__(self, ax, label, valmin, valmax, valinit, **kwargs):
@@ -13,6 +15,13 @@ def drag_active(self):
 
     @drag_active.setter
     def drag_active(self, value):
+        if self._drag_active is value:
+            return
         self._drag_active = value
-        if value is False and self.on_release:
-            self.on_release(self.val)
+        if value:
+            enter_dragging()
+        else:
+            if self.on_release:
+                with releasing():
+                    self.on_release(self.val)
+            exit_dragging()
diff --git a/pyquibbler/quib/factory.py b/pyquibbler/quib/factory.py
index 44adbb3a4..fcb39a967 100644
--- a/pyquibbler/quib/factory.py
+++ b/pyquibbler/quib/factory.py
@@ -110,8 +110,7 @@ def create_quib(func: Optional[Callable],
 
     # register new quib on parents
     if register_as_child_of_parents:
-        for parent in quib.parents:
-            parent.handler.add_child(quib)
+        quib.handler.connect_to_parents()
 
     # evaluate now if not lazy
     lazy = func_definition.lazy if lazy is NoValue else lazy
diff --git a/pyquibbler/quib/func_calling/cached_quib_func_call.py b/pyquibbler/quib/func_calling/cached_quib_func_call.py
index ca61fbe45..e84755df2 100644
--- a/pyquibbler/quib/func_calling/cached_quib_func_call.py
+++ b/pyquibbler/quib/func_calling/cached_quib_func_call.py
@@ -82,7 +82,7 @@ def _run_single_call(self, func: Callable, graphics_collection: GraphicsCollecti
             res = res.get_value()
 
         if self.artists_creation_callback:
-            self.artists_creation_callback(set(graphics_collection.artists))
+            self.artists_creation_callback(graphics_collection.artists, self.func_args_kwargs)
 
         return res
 
diff --git a/pyquibbler/quib/func_calling/func_calls/known_graphics/widgets/slider_call.py b/pyquibbler/quib/func_calling/func_calls/known_graphics/widgets/slider_call.py
index d1ec845ad..bc2e4d640 100644
--- a/pyquibbler/quib/func_calling/func_calls/known_graphics/widgets/slider_call.py
+++ b/pyquibbler/quib/func_calling/func_calls/known_graphics/widgets/slider_call.py
@@ -12,4 +12,6 @@ def _on_change_slider(self, new_value):
 
     def _connect_callbacks(self, widget: QSlider):
         widget.on_changed(self._on_change_slider)
-        # widget.on_release = self._on_change_slider  # disabled. it caused the overriding dialog to appear twice.
+
+        # TODO: this line perhaps should be disabled. it causes the overriding dialog to appear twice.
+        widget.on_release = self._on_change_slider
diff --git a/pyquibbler/quib/func_calling/quib_func_call.py b/pyquibbler/quib/func_calling/quib_func_call.py
index 621d8d8c7..9a936406c 100644
--- a/pyquibbler/quib/func_calling/quib_func_call.py
+++ b/pyquibbler/quib/func_calling/quib_func_call.py
@@ -24,12 +24,12 @@ class QuibFuncCall(FuncCall):
     by caching results and only asking for necessary values from argument quibs
     """
 
-    artists_creation_callback: Callable = None
+    artists_creation_callback: Optional[Callable] = None
     graphics_collections: Optional[np.ndarray] = None
     method_cache: Mapping[CachedCall, Any] = field(default_factory=dict)
     cache: Optional[Cache] = None
     _caching: bool = False
-    _result_metadata: ResultMetadata = None
+    _result_metadata: Optional[ResultMetadata] = None
     cache_mode: CacheMode = None
 
     SOURCE_OBJECT_TYPE = Quib
@@ -104,6 +104,7 @@ def func_can_create_graphics(self):
 
     def on_type_change(self):
         self.method_cache.clear()
+        self._result_metadata = None
 
     def invalidate_cache_at_path(self, path: Path):
         pass
diff --git a/pyquibbler/quib/graphics/artist_wrapper.py b/pyquibbler/quib/graphics/artist_wrapper.py
new file mode 100644
index 000000000..d16a759bf
--- /dev/null
+++ b/pyquibbler/quib/graphics/artist_wrapper.py
@@ -0,0 +1,51 @@
+from typing import Optional, Dict
+from matplotlib.artist import Artist
+
+from pyquibbler.quib.quib import Quib
+
+SETTER_QUIBS_NAME = '_quibbler_artist_setter_quibs'
+CREATING_QUIB_NAME = '_quibbler_artist_creating_quib'
+
+
+def get_creating_quib(artist: Artist):
+    return getattr(artist, CREATING_QUIB_NAME, None)
+
+
+def delete_creating_quib_attr(artist: Artist):
+    if hasattr(artist, CREATING_QUIB_NAME):
+        delattr(artist, CREATING_QUIB_NAME)
+
+
+def set_creating_quib(artist: Artist, quib: Optional[Quib]):
+    if quib is None:
+        delete_creating_quib_attr(artist)
+    else:
+        setattr(artist, CREATING_QUIB_NAME, quib)
+
+
+def get_all_setter_quibs(artist: Artist) -> Dict[str, Quib]:
+    if not hasattr(artist, SETTER_QUIBS_NAME):
+        setattr(artist, SETTER_QUIBS_NAME, dict())
+    return getattr(artist, SETTER_QUIBS_NAME)
+
+
+def get_setter_quib(artist: Artist, name: str) -> Optional[Quib]:
+    return get_all_setter_quibs(artist).get(name, None)
+
+
+def set_setter_quib(artist: Artist, name: str, quib: Optional[Quib]):
+    setter_quibs = get_all_setter_quibs(artist)
+    if quib is None:
+        setter_quibs.pop(name, None)
+    else:
+        setter_quibs[name] = quib
+
+
+def delete_setting_quibs_attr(artist):
+    if hasattr(artist, SETTER_QUIBS_NAME):
+        delattr(artist, SETTER_QUIBS_NAME)
+
+
+def clear_all_quibs(artist):
+    delete_creating_quib_attr(artist)
+    delete_setting_quibs_attr(artist)
diff --git a/pyquibbler/quib/graphics/event_handling/canvas_event_handler.py b/pyquibbler/quib/graphics/event_handling/canvas_event_handler.py
index 391a70b41..8ec33806c 100644
--- a/pyquibbler/quib/graphics/event_handling/canvas_event_handler.py
+++ b/pyquibbler/quib/graphics/event_handling/canvas_event_handler.py
@@ -1,7 +1,6 @@
 from contextlib import contextmanager
 from threading import Lock
 from typing import Optional, Tuple, Callable
-from matplotlib.artist import Artist
 from matplotlib.backend_bases import MouseEvent, PickEvent, MouseButton
 
 from pyquibbler.env import END_DRAG_IMMEDIATELY
@@ -11,6 +10,7 @@
 from pyquibbler.quib.graphics.redraw import aggregate_redraw_mode
 from pyquibbler.quib.graphics.event_handling import graphics_inverse_assigner
 from pyquibbler.assignment.override_choice.types import OverrideGroup
+from pyquibbler.quib.graphics import artist_wrapper
 
 from matplotlib.axes import Axes
 from pyquibbler.quib import Quib
@@ -58,6 +58,7 @@ def get_or_create_initialized_event_handler(cls, canvas):
     def __init__(self, canvas):
         self.canvas = canvas
         self.current_pick_event: Optional[PickEvent] = None
+        self.current_pick_quib: Optional[Quib] = None
         self._last_mouse_event_with_overrides: Optional[PickEvent] = None
         self._last_axis_event_with_overrides = {}
         self._assignment_lock = Lock()
@@ -78,6 +79,7 @@ def _handle_button_release(self, _mouse_event: MouseEvent):
                 self._inverse_from_mouse_event(self._last_mouse_event_with_overrides)
         self._last_mouse_event_with_overrides = None
         self.current_pick_event = None
+        self.current_pick_quib = None
 
         if self._last_axis_event_with_overrides:
             with releasing():
@@ -90,10 +92,11 @@ def _handle_button_release(self, _mouse_event: MouseEvent):
 
     def _handle_pick_event(self, pick_event: PickEvent):
         self.current_pick_event = pick_event
+        self.current_pick_quib = artist_wrapper.get_creating_quib(pick_event.artist)
         if pick_event.mouseevent.button is MouseButton.RIGHT:
             self._inverse_from_mouse_event(pick_event.mouseevent)
 
-    def _inverse_assign_graphics(self, artist: Artist, mouse_event: MouseEvent):
+    def _inverse_assign_graphics(self, mouse_event: MouseEvent):
         """
         Reverse any relevant quibs in artists creation args
         """
@@ -102,13 +105,13 @@ def _inverse_assign_graphics(self, artist: Artist, mouse_event: MouseEvent):
         if pick_event is None:
             # This case was observed in the wild
             return
-        drawing_func = getattr(artist, '_quibbler_drawing_func')
-        args = getattr(artist, '_quibbler_args')
+
+        drawing_quib = self.current_pick_quib
         with timer("motion_notify", lambda x: logger.info(f"motion notify {x}")), \
                 aggregate_redraw_mode(), \
                 graphics_assignment_mode(mouse_event.inaxes):
-            override_group = graphics_inverse_assigner.inverse_assign_drawing_func(drawing_func=drawing_func,
-                                                                                   args=args,
+            override_group = graphics_inverse_assigner.inverse_assign_drawing_func(drawing_func=drawing_quib.func,
+                                                                                   args=drawing_quib.args,
                                                                                    mouse_event=mouse_event,
                                                                                    pick_event=pick_event)
             if override_group is not None and override_group:
@@ -154,15 +157,14 @@ def _handle_motion_notify(self, mouse_event: MouseEvent):
 
     def _inverse_from_mouse_event(self, mouse_event):
         if self.current_pick_event is not None:
-            drawing_func = getattr(self.current_pick_event.artist, '_quibbler_drawing_func', None)
-            if drawing_func is not None:
-                with self._try_acquire_assignment_lock() as locked:
-                    if locked:
-                        # If not locked, there is already another motion handler running, we just drop this one.
-                        # This could happen if changes are slow or if a dialog is open
-                        self._inverse_assign_graphics(self.current_pick_event.artist, mouse_event)
-                        if END_DRAG_IMMEDIATELY:
-                            self.current_pick_event = None
+            with self._try_acquire_assignment_lock() as locked:
+                if locked:
+                    # If not locked, there is already another motion handler running, we just drop this one.
+                    # This could happen if changes are slow or if a dialog is open
+                    self._inverse_assign_graphics(mouse_event)
+                    if END_DRAG_IMMEDIATELY:
+                        self.current_pick_event = None
+                        self.current_pick_quib = None
 
     def initialize(self):
         """
@@ -175,8 +177,12 @@ def initialize(self):
         def disconnect(event):
             for handler_id in handler_ids:
                 self.canvas.mpl_disconnect(handler_id)
+
             self.CANVASES_TO_TRACKERS.pop(self.canvas)
 
+            for ax in self.canvas.figure.axes:
+                ax.cla()
+
         handler_ids.append(self.canvas.mpl_connect('close_event', disconnect))
 
     def handle_axes_limits_changed(self, ax: Axes, drawing_func: Callable, lim: Tuple[float, float]):
@@ -184,8 +190,8 @@ def handle_axes_limits_changed(self, ax: Axes, drawing_func: Callable, lim: Tupl
         This method is called by the overridden set_xlim, set_ylim
         """
         from pyquibbler.graphics import dragging
-        name = f'_quibbler_{drawing_func.__name__}'
-        set_lim_quib = getattr(ax, name, None)
+        name = drawing_func.__name__
+        set_lim_quib = artist_wrapper.get_setter_quib(ax, name)
         if isinstance(set_lim_quib, Quib):
             with self._try_acquire_assignment_lock() as locked:
                 if locked:
diff --git a/pyquibbler/quib/graphics/persist.py b/pyquibbler/quib/graphics/persist.py
index 2405ab8a9..a55c752dd 100644
--- a/pyquibbler/quib/graphics/persist.py
+++ b/pyquibbler/quib/graphics/persist.py
@@ -1,73 +1,64 @@
-from itertools import chain
-from typing import Set, Callable, Iterable, Any
-
-from matplotlib.artist import Artist
+from __future__ import annotations
+import dataclasses
+from abc import ABC, abstractmethod
+from typing import Iterable
 
+from pyquibbler.quib.graphics import artist_wrapper
 from pyquibbler.quib.graphics.event_handling import CanvasEventHandler
-from pyquibbler.quib.utils.iterators import iter_object_type_in_args
-from pyquibbler.quib.quib import Quib
-
 
-def save_func_and_args_on_artists(artist: Artist, func: Callable, args: Iterable[Any]):
-    """
-    Set the drawing func and on the artist- this will be used later on when tracking an artist, in order to know how to
-    inverse and handle events.
-    If there's already a creating func, we assume the lower func that already created the artist is the actual
-    drawing func (such as a user func that called plt.plot)
-    """
-    if getattr(artist, '_quibbler_drawing_func', None) is None:
-        artist._quibbler_drawing_func = func
-        artist._quibbler_args = args
+from weakref import ReferenceType
+from pyquibbler.quib.quib import Quib
+from pyquibbler.function_definitions import FuncArgsKwargs
+from matplotlib.artist import Artist
 
 
-def track_artist(artist: Artist):
+def track_artist(artist):
     CanvasEventHandler.get_or_create_initialized_event_handler(artist.figure.canvas)
 
 
-def persist_func_on_artists(quib: Quib, new_artists):
-    for artist in new_artists:
-        save_func_and_args_on_artists(artist, func=quib.func, args=quib.args)
-        track_artist(artist)
-
-
-def persist_quib_on_artists(quib: Quib, new_artists: Set[Artist]):
-    """
-    Persist self on all artists we're connected to, making sure we won't be garbage collected until they are
-    off the screen
-    We need to also go over args as there may be a situation in which the function did not create new artists, but
-    did perform an action on an existing one, such as Axes.set_xlim
-    """
-
-    artists_to_persist_on = new_artists \
-        if new_artists else quib.handler.quib_function_call.get_objects_of_type_in_args_kwargs(Artist)
-    for artist in artists_to_persist_on:
-        quibs = getattr(artist, '_quibbler_graphics_function_quibs', set())
-        quibs.add(quib)
-        artist._quibbler_graphics_function_quibs = quibs
-
-
-def persist_relevant_info_on_new_artists_for_quib(quib: Quib, new_artists):
-    persist_quib_on_artists(quib, new_artists)
-    persist_func_on_artists(quib, new_artists)
-
-
-def persist_artists_on_quib_weak_ref(weak_ref_quib, artists):
-    """
-    This method will be given as a callback to the function runner whenever it creates artists.
-
-    This can't be a method on the quib, because the callback has to be with a `weakref` so that we won't hold have a
-    circular reference between the quib and the FuncRunner- The quib should hold the FuncRunner, and the
-    functionrunner should have no knowledge of it
-    """
-    quib: Quib = weak_ref_quib()
-    if quib.handler.func_definition.replace_previous_quibs_on_artists:
-        persist_func_on_artists(quib, artists)
-        for artist in chain(artists, iter_object_type_in_args(Artist, quib.args, quib.kwargs)):
-            name = f'_quibbler_{quib.func.__name__}'
-            current_quib = getattr(artist, name, None)
-            if current_quib is not quib and current_quib is not None:
-                for parent in current_quib.parents:
-                    parent.handler.remove_child(current_quib)
-            setattr(artist, name, quib)
-    else:
-        persist_relevant_info_on_new_artists_for_quib(quib, artists)
+@dataclasses.dataclass
+class RunFunctionWithQuibArg(ABC):
+    weak_ref_quib: ReferenceType[Quib]
+
+    @property
+    def quib(self):
+        return self.weak_ref_quib()
+
+    def __call__(self, *args, **kwargs):
+        return self.called_function(self.quib, *args, **kwargs)
+
+    @staticmethod
+    @abstractmethod
+    def called_function(quib: Quib, *args, **kwargs):
+        pass
+
+
+class PersistQuibOnCreatedArtists(RunFunctionWithQuibArg):
+
+    @staticmethod
+    def called_function(quib: Quib, new_artists: Iterable[Artist], func_args_kwargs: FuncArgsKwargs):
+        """
+        Persist a graphic creating quib on the artists that it creates
+        This method will be given as a callback to the function runner whenever it creates artists.
+        """
+        for artist in new_artists:
+            artist_wrapper.set_creating_quib(artist, quib)
+            track_artist(artist)
+
+
+class PersistQuibOnSettedArtist(RunFunctionWithQuibArg):
+
+    @staticmethod
+    def called_function(quib: Quib, new_artists: Iterable[Artist], func_args_kwargs: FuncArgsKwargs):
+        """
+        Persist a graphic-setting quib on the artist whose properties it sets
+        This method will be given as a callback to the function runner whenever it creates artists.
+        """
+        if func_args_kwargs.args and isinstance(func_args_kwargs.args[0], Artist):
+            setted_artist = func_args_kwargs.args[0]
+            track_artist(setted_artist)
+            name = quib.func.__name__
+            old_quib = artist_wrapper.get_setter_quib(setted_artist, name)
+            if old_quib is not quib and old_quib is not None:
+                old_quib.handler.disconnect_from_parents()
+            artist_wrapper.set_setter_quib(setted_artist, name, quib)
diff --git a/pyquibbler/quib/quib.py b/pyquibbler/quib/quib.py
index daf7d9e2f..31fb97a5d 100644
--- a/pyquibbler/quib/quib.py
+++ b/pyquibbler/quib/quib.py
@@ -2,7 +2,6 @@
 
 import copy
 import enum
-import functools
 import pathlib
 import pickle
 import weakref
@@ -15,7 +14,7 @@
 from pyquibbler.quib.types import FileAndLineNumber
 from pyquibbler.utilities.file_path import PathWithHyperLink
 from functools import cached_property
-from typing import Set, Any, TYPE_CHECKING, Optional, Tuple, Type, List, Union, Iterable, Mapping, Callable
+from typing import Set, Any, TYPE_CHECKING, Optional, Tuple, Type, List, Union, Iterable, Mapping, Callable, Iterator
 from weakref import WeakSet
 
 from matplotlib.artist import Artist
@@ -28,8 +27,7 @@
 from pyquibbler.quib.pretty_converters import MathExpression, FailedMathExpression, \
     NameMathExpression, pretty_convert
 from pyquibbler.quib.utils.miscellaneous import copy_and_replace_quibs_with_vals, NoValue
-from pyquibbler.quib.utils.translation_utils import get_func_call_for_translation_with_sources_metadata, \
-    get_func_call_for_translation_without_sources_metadata
+from pyquibbler.quib.utils.translation_utils import get_func_call_for_translation_with_sources_metadata
 from pyquibbler.utilities.input_validation_utils import validate_user_input, InvalidArgumentValueException, \
     get_enum_by_str
 from pyquibbler.utilities.iterators import recursively_run_func_on_object, recursively_compare_objects_type, \
@@ -124,6 +122,10 @@ def quib(self):
     def project(self) -> Project:
         return Project.get_or_create()
 
+    @property
+    def parents(self) -> Iterator[Quib]:
+        return self.quib_function_call.get_data_sources() | self.quib_function_call.get_parameter_sources()
+
     def add_child(self, quib: Quib) -> None:
         """
         Add the given quib to the list of quibs that are dependent on this quib.
@@ -136,6 +138,20 @@ def remove_child(self, quib_to_remove: Quib):
         """
         self.children.remove(quib_to_remove)
 
+    def connect_to_parents(self):
+        """
+        Connect the quib to its parents
+        """
+        for parent in self.parents:
+            parent.handler.add_child(self.quib)
+
+    def disconnect_from_parents(self):
+        """
+        Disconnect the quib from its parents, so that the quib is effectively inactivated
+        """
+        for parent in self.parents:
+            parent.handler.remove_child(self.quib)
+
     def get_descendants(self):
         children = set(self.children)  # copy to prevent set changing during operation
         for child in self.children:
@@ -160,7 +176,7 @@ def redraw_if_appropriate(self) -> None:
         """
         graphics_update = self.actual_graphics_update
         if graphics_update == GraphicsUpdateType.DRAG \
-                or self.graphics_update == GraphicsUpdateType.DROP and not is_within_drag():
+                or graphics_update == GraphicsUpdateType.DROP and not is_within_drag():
             self.quib.get_value()
 
     def _iter_artist_lists(self) -> Iterable[List[Artist]]:
@@ -237,19 +253,6 @@ def _invalidate_quib_with_children_at_path(self, invalidator_quib: Quib, path: P
                 if len(path) == 0 or len(self._get_list_of_not_overridden_paths_at_first_component(new_path)) > 0:
                     self._invalidate_children_at_path(new_path)
 
-    def _forward_translate_without_retrieving_metadata(self, invalidator_quib: Quib, path: Path) -> Paths:
-        func_call, sources_to_quibs = get_func_call_for_translation_without_sources_metadata(
-            self.quib_function_call
-        )
-        quibs_to_sources = {quib: source for source, quib in sources_to_quibs.items()}
-        sources_to_forwarded_paths = forwards_translate(
-            func_call=func_call,
-            sources_to_paths={
-                quibs_to_sources[invalidator_quib]: path
-            },
-        )
-        return sources_to_forwarded_paths.get(quibs_to_sources[invalidator_quib], [])
-
     def _forward_translate_with_retrieving_metadata(self, invalidator_quib: Quib, path: Path) -> Paths:
         func_call, sources_to_quibs = get_func_call_for_translation_with_sources_metadata(
             self.quib_function_call
@@ -266,19 +269,6 @@ def _forward_translate_with_retrieving_metadata(self, invalidator_quib: Quib, pa
         )
         return sources_to_forwarded_paths.get(quibs_to_sources[invalidator_quib], [])
 
-    def _forward_translate_source_path(self, invalidator_quib: Quib, path: Path) -> Paths:
-        """
-        Forward translate a path, first attempting to do it WITHOUT using getting the shape and type, and if/when
-        failure does grace us, we attempt again with shape and type
-        """
-        try:
-            return self._forward_translate_without_retrieving_metadata(invalidator_quib, path)
-        except NoTranslatorsFoundException:
-            try:
-                return self._forward_translate_with_retrieving_metadata(invalidator_quib, path)
-            except NoTranslatorsFoundException:
-                return [[]]
-
     def _get_paths_for_children_invalidation(self, invalidator_quib: Quib,
                                              path: Path) -> Paths:
         """
@@ -287,16 +277,15 @@ def _get_paths_for_children_invalidation(self, invalidator_quib: Quib,
         If we have no translators, we forward the path to invalidate all, as we have no more specific way to do it
         """
         # We always invalidate all if it's a parameter source quib
-        if invalidator_quib not in self.quib_function_call.get_data_sources():
+        if invalidator_quib not in self.quib_function_call.get_data_sources() \
+                or path == [] \
+                or not self.quib_function_call._result_metadata:
             return [[]]
 
         try:
-            return self._forward_translate_without_retrieving_metadata(invalidator_quib, path)
+            return self._forward_translate_with_retrieving_metadata(invalidator_quib, path)
         except NoTranslatorsFoundException:
-            try:
-                return self._forward_translate_with_retrieving_metadata(invalidator_quib, path)
-            except NoTranslatorsFoundException:
-                return [[]]
+            return [[]]
 
     def reset_quib_func_call(self):
         definition = get_definition_for_function(self.func_args_kwargs.func)
@@ -305,9 +294,10 @@ def reset_quib_func_call(self):
             func_definition=self.func_definition,
             cache_mode=self.cache_mode,
         )
-        from pyquibbler.quib.graphics.persist import persist_artists_on_quib_weak_ref
-        self.quib_function_call.artists_creation_callback = functools.partial(persist_artists_on_quib_weak_ref,
-                                                                              weakref.ref(self.quib))
+        from pyquibbler.quib.graphics.persist import PersistQuibOnCreatedArtists, PersistQuibOnSettedArtist
+        persist_quib_callback = PersistQuibOnSettedArtist if definition.is_artist_setter \
+            else PersistQuibOnCreatedArtists
+        self.quib_function_call.artists_creation_callback = persist_quib_callback(weakref.ref(self.quib))
 
     """
     assignments
@@ -915,7 +905,8 @@ def is_graphics_quib(self) -> bool:
         is_graphics, graphics_update
         pyquibbler.refresh_graphics
         """
-        return self.handler.quib_function_call.func_can_create_graphics
+        return self.handler.quib_function_call.func_can_create_graphics \
+            and not self.handler.created_in_get_value_context
 
     @property
     def graphics_update(self) -> GraphicsUpdateType:
@@ -1462,7 +1453,7 @@ def parents(self) -> Set[Quib]:
         --------
         ancestors, children, descendants
         """
-        return set(self.handler.quib_function_call.get_objects_of_type_in_args_kwargs(Quib))
+        return set(self.handler.parents)
 
     @cached_property
     def ancestors(self) -> Set[Quib]:
diff --git a/pyquibbler/quib/utils/iterators.py b/pyquibbler/quib/utils/iterators.py
index f03f55e39..390ed6d6b 100644
--- a/pyquibbler/quib/utils/iterators.py
+++ b/pyquibbler/quib/utils/iterators.py
@@ -1,6 +1,6 @@
 from typing import Optional, Any, Mapping, Tuple
 from pyquibbler.utilities.iterators import iter_objects_of_type_in_object_recursively, iter_objects_of_type_in_object, \
-    iter_object_type_in_args
+    iter_object_type_in_args_kwargs
 
 
 # Most common use-cases require one level of scanning - for example a quib inside a shape tuple.
@@ -29,4 +29,4 @@ def iter_quibs_in_args(args: Tuple[Any, ...], kwargs: Mapping[str, Any]):
     Returns an iterator for all quib objects nested in the given args and kwargs.
     """
     from pyquibbler.quib.quib import Quib
-    return iter_object_type_in_args(Quib, args, kwargs)
+    return iter_object_type_in_args_kwargs(Quib, args, kwargs)
diff --git a/pyquibbler/utilities/iterators.py b/pyquibbler/utilities/iterators.py
index c991daf79..8c211712d 100644
--- a/pyquibbler/utilities/iterators.py
+++ b/pyquibbler/utilities/iterators.py
@@ -1,5 +1,3 @@
-import functools
-import itertools
 import numpy as np
 from typing import Tuple, Any, Mapping, Type, Optional, Callable
 
@@ -82,12 +80,11 @@ def iter_objects_of_type_in_object(object_type: Type, obj: Any, force_recursive:
     return result
 
 
-def iter_object_type_in_args(object_type, args: Tuple[Any, ...], kwargs: Mapping[str, Any]):
+def iter_object_type_in_args_kwargs(object_type, args: Tuple[Any, ...], kwargs: Mapping[str, Any]):
     """
     Returns an iterator for all objects of a type nested in the given args and kwargs.
     """
-    return itertools.chain(*map(functools.partial(iter_objects_of_type_in_object, object_type),
-                                itertools.chain(args, kwargs.values())))
+    return iter_objects_of_type_in_object(object_type, (*args, *kwargs.values()))
 
 
 def iter_args_and_names_in_function_call(func: Callable, args: Tuple[Any, ...], kwargs: Mapping[str, Any],
diff --git a/setup.py b/setup.py
index ff892fe4f..89b0a8bfa 100644
--- a/setup.py
+++ b/setup.py
@@ -10,7 +10,8 @@
         'dev': [
             'pytest',
             'pytest-pep8',
-            'pytest-cov'
+            'pytest-cov',
+            'pytest-benchmark',
         ],
         'sphinx': [
             'sphinx',


Test patch:
diff --git a/tests/functional/assignment/test_overrider.py b/tests/functional/assignment/test_overrider.py
index fd55a9810..c96001cb8 100644
--- a/tests/functional/assignment/test_overrider.py
+++ b/tests/functional/assignment/test_overrider.py
@@ -35,7 +35,7 @@ def test_overrider_with_global_override_and_partial_assignments(overrider):
 
 
 def test_overrider_with_field_assignment(overrider):
-    dtype = [('name', np.unicode, 21), ('age', np.int_)]
+    dtype = [('name', np.compat.unicode, 21), ('age', np.int_)]
     overrider.add_assignment(Assignment(value=1, path=[PathComponent(indexed_cls=np.ndarray, component='age')]))
     new_data = overrider.override(np.array([('maor2', 23)], dtype=dtype))
 
@@ -43,7 +43,7 @@ def test_overrider_with_field_assignment(overrider):
 
 
 def test_overrider_with_field_assignment_and_indices(overrider):
-    dtype = [('name', np.unicode, 21), ('age', np.int_)]
+    dtype = [('name', np.compat.unicode, 21), ('age', np.int_)]
     overrider.add_assignment(Assignment(value=1, path=[PathComponent(indexed_cls=np.ndarray,
                                                                      component=(0, 0)),
                                                        PathComponent(indexed_cls=np.ndarray,
diff --git a/tests/functional/quib/graphics/event_handling/test_canvas_event_handler.py b/tests/functional/quib/graphics/event_handling/test_canvas_event_handler.py
index 9612e1791..53bb030b7 100644
--- a/tests/functional/quib/graphics/event_handling/test_canvas_event_handler.py
+++ b/tests/functional/quib/graphics/event_handling/test_canvas_event_handler.py
@@ -22,6 +22,8 @@ def test_canvas_event_handler_delete_itself_upon_figure_close():
 
     canvas = mock.Mock()
     canvas.events_to_funcs = {}
+    canvas.figure = mock.Mock()
+    canvas.figure.axes = []
 
     def mpl_connect(event, func):
         canvas.events_to_funcs[event] = func
@@ -60,15 +62,16 @@ def test_canvas_event_handler_plot_drag_without_pick_event_does_nothing(canvas_e
 
 def test_canvas_event_handler_plot_drag(canvas_event_handler, mock_inverse_graphics_function):
     pick_event = mock.Mock()
-    drawing_func = mock.Mock()
-    pick_event.artist._quibbler_drawing_func = drawing_func
-    pick_event.artist._quibbler_args = [mock.Mock()]
+    drawing_quib = mock.Mock()
+    drawing_quib.func = mock.Mock()
+    drawing_quib.args = mock.Mock()
+    pick_event.artist._quibbler_artist_creating_quib = drawing_quib
     canvas_event_handler._handle_pick_event(pick_event)
     mouse_event = mock.Mock()
     canvas_event_handler._handle_motion_notify(mouse_event)
 
-    mock_inverse_graphics_function.assert_called_once_with(drawing_func=drawing_func,
-                                                           args=pick_event.artist._quibbler_args,
+    mock_inverse_graphics_function.assert_called_once_with(drawing_func=drawing_quib.func,
+                                                           args=drawing_quib.args,
                                                            mouse_event=mouse_event,
                                                            pick_event=pick_event)
 
diff --git a/tests/functional/quib/graphics/test_redraw.py b/tests/functional/quib/graphics/test_redraw.py
index c50327e01..7757b4eeb 100644
--- a/tests/functional/quib/graphics/test_redraw.py
+++ b/tests/functional/quib/graphics/test_redraw.py
@@ -3,6 +3,7 @@
 from unittest import mock
 from unittest.mock import Mock
 
+import numpy as np
 import pytest
 
 from pyquibbler import iquib
@@ -10,7 +11,7 @@
 from pyquibbler.function_definitions.func_definition import create_func_definition
 from pyquibbler.quib.factory import create_quib
 from pyquibbler.quib.graphics.redraw import aggregate_redraw_mode, redraw_axeses
-
+from weakref import ref
 
 
 @pytest.fixture
@@ -44,7 +45,7 @@ def test_redraw_in_aggregate_mode():
     quib = iquib(1)
     add_definition_for_function(func=mock_func, func_definition=create_func_definition(is_graphics=True,
                                                                                        lazy=True,
-                                                                                       replace_previous_quibs_on_artists=True))
+                                                                                       is_artist_setter=True))
     _ = create_quib(func=mock_func, args=(quib,))
     assert mock_func.call_count == 0, "sanity"
 
@@ -62,8 +63,49 @@ def test_redraw_axeses_does_not_redraw_same_canvas_twice(figure, axes1, axes2):
     figure.canvas.draw.assert_called_once()
 
 
+# To prevent pyimageXX bug in TK on notebook. see issue: #119
+@pytest.mark.regression
 def test_redraw_after_figure_closed(figure, axes1):
     plt.close(figure)
     redraw_axeses({axes1})
     figure.canvas.draw.assert_not_called()
 
+
+# this test only works on jupyter notebook:
+#
+# def test_quibs_deleted_after_figure_closed():
+#     figure = plt.figure()
+#     axes1 = figure.gca()
+#     a = iquib(np.array([1, 2]))
+#     b = axes1.plot(a, picker=True)
+#     ref_a = ref(a)
+#     ref_b = ref(b)
+#     del a, b
+#     plt.close(figure)
+#
+#     assert ref_a() is None
+#     assert ref_b() is None
+
+
+def test_quibs_deleted_after_figure_clf(figure, axes1):
+    a = iquib(np.array([1, 2]))
+    b = axes1.plot(a, picker=True)
+    ref_a = ref(a)
+    ref_b = ref(b)
+    del a, b
+    figure.clf()
+
+    assert ref_a() is None
+    assert ref_b() is None
+
+
+def test_quibs_deleted_after_axes_cla(figure, axes1):
+    a = iquib(np.array([1, 2]))
+    b = axes1.plot(a, picker=True)
+    ref_a = ref(a)
+    ref_b = ref(b)
+    del a, b
+    axes1.cla()
+
+    assert ref_a() is None
+    assert ref_b() is None
diff --git a/tests/functional/quib/test_quib/get_value/test_vectorize_call.py b/tests/functional/quib/test_quib/get_value/test_vectorize_call.py
index b29e40901..fdd440111 100644
--- a/tests/functional/quib/test_quib/get_value/test_vectorize_call.py
+++ b/tests/functional/quib/test_quib/get_value/test_vectorize_call.py
@@ -117,7 +117,7 @@ def test_lazy_vectorize():
     parent[0] = 100
     parent[1] = 101
     parent[2] = 102
-    func_mock.assert_called_once()
+    func_mock.assert_not_called()
 
 
 def test_vectorize_doesnt_evaluate_sample_when_getting_value():
diff --git a/tests/functional/quib/test_quib/invalidation/test_general.py b/tests/functional/quib/test_quib/invalidation/test_general.py
index 86a15fd6d..8c99b247c 100644
--- a/tests/functional/quib/test_quib/invalidation/test_general.py
+++ b/tests/functional/quib/test_quib/invalidation/test_general.py
@@ -14,7 +14,8 @@ def test_quib_does_not_request_shape_or_parents_shapes_on_first_attempt(create_m
     forwards_path_translator = mock.Mock()
     forwards_path_translator.return_value.translate.return_value = {}
     func.func_definition = create_func_definition(raw_data_source_arguments=[0],
-                                                      forwards_path_translators=[forwards_path_translator])
+                                                  forwards_path_translators=[forwards_path_translator],
+                                                  func=func)
     parent_quib = create_quib(func=func, args=(1,))
     quib = create_quib(func=lambda a: a, args=(parent_quib,))
     parent_quib.handler.invalidate_and_redraw_at_path([])
@@ -36,4 +37,17 @@ def test_diamond_invalidation_with_changing_shape(create_quib_with_return_value)
     # This previously raised an exception
     a.assign(10)
 
-    assert np.array_equal(e.get_value(), np.arange(10) * 2)
\ No newline at end of file
+    assert np.array_equal(e.get_value(), np.arange(10) * 2)
+
+
+@pytest.mark.regression
+def test_invalidation_does_not_request_shape(create_quib_with_return_value):
+    func = mock.Mock(return_value=7)
+
+    a = create_quib_with_return_value([0, 1, 2], allow_overriding=True)
+    b = np.vectorize(func)(a)
+
+    func.assert_not_called(), "sanity"
+
+    a[0] = 7
+    func.assert_not_called(),
diff --git a/tests/functional/quib/test_quib/invalidation/test_transpositional.py b/tests/functional/quib/test_quib/invalidation/test_transpositional.py
index 78ac30772..da72a521c 100644
--- a/tests/functional/quib/test_quib/invalidation/test_transpositional.py
+++ b/tests/functional/quib/test_quib/invalidation/test_transpositional.py
@@ -123,7 +123,7 @@ def test_invalidate_embedded_dicts_should_invalidate(quib_dict):
 
 @pytest.fixture()
 def quib_with_nested_arr():
-    dtype = [('nested', [('child_name', np.unicode, 30)], (2,))]
+    dtype = [('nested', [('child_name', np.compat.unicode, 30)], (2,))]
     return iquib(np.array(['Yechiel', "Yossiel"], dtype=dtype))
 
 
diff --git a/tests/functional/quib/test_quib/test_graphics.py b/tests/functional/quib/test_quib/test_graphics.py
index 2dc0a8bcf..312b38d97 100644
--- a/tests/functional/quib/test_quib/test_graphics.py
+++ b/tests/functional/quib/test_quib/test_graphics.py
@@ -117,24 +117,23 @@ def replacing_func():
     mock_func.__name__ = "myfunc"
     add_definition_for_function(func=mock_func, func_definition=create_func_definition(
         is_graphics=True,
-        replace_previous_quibs_on_artists=True),
+        is_artist_setter=True),
                                 )
     return mock_func
 
 
-def test_replacing_graphics_function_quib(create_quib_with_return_value, replacing_func):
+def test_replacing_graphics_function_quib(axes, create_quib_with_return_value, replacing_func):
     first_quib = create_quib_with_return_value(5)
-    mock_artist = mock.Mock(spec=Axes)
     # Creating runs the quibs- it's also important to keep them as a local var so they don't get garbage collected
     # if they are the test will pass regardless
     _ = create_quib(
         func=replacing_func,
-        args=(mock_artist, first_quib),
+        args=(axes, first_quib),
         lazy=False
     )
     __ = create_quib(
         func=replacing_func,
-        args=(mock_artist,),
+        args=(axes,),
         lazy=False
     )
 
@@ -145,16 +144,15 @@ def test_replacing_graphics_function_quib(create_quib_with_return_value, replaci
 
 @pytest.mark.regression
 def test_replacing_graphics_function_quib_doesnt_remove_quib_after_invalidation_three_times(
-        create_quib_with_return_value, replacing_func
+        axes, create_quib_with_return_value, replacing_func
 ):
     first_quib = create_quib_with_return_value(5)
-    mock_artist = mock.Mock(spec=Axes)
     path = [PathComponent(component=..., indexed_cls=first_quib.get_type())]
 
     # First time to create quib, attach to parent, attach to axes
     _ = create_quib(
         func=replacing_func,
-        args=(mock_artist, first_quib),
+        args=(axes, first_quib),
         lazy=False,
     )
     # Second time to potentially remove from axes (this was the bug)
diff --git a/tests/functional/quib/test_quib/test_inversal.py b/tests/functional/quib/test_quib/test_inversal.py
index ef984812a..2c7027f2e 100644
--- a/tests/functional/quib/test_quib/test_inversal.py
+++ b/tests/functional/quib/test_quib/test_inversal.py
@@ -37,7 +37,7 @@ def test_inverse_assign_field_array_with_function_and_fancy_indexing_and_field_n
 
 
 def test_inverse_assign_nested_with_fancy_rot90_fancy_and_replace(create_quib_with_return_value):
-    dtype = [('name', '|S10'), ('nested', [('child_name', np.unicode, 30)], (3,))]
+    dtype = [('name', '|S10'), ('nested', [('child_name', np.compat.unicode, 30)], (3,))]
     name_1 = 'Maor'
     name_2 = 'StupidMan'
     first_children = ['Yechiel', "Yossiel", "Yirmiyahu"]
diff --git a/tests/functional/quib/test_quib/test_quib_persistence.py b/tests/functional/quib/test_quib/test_quib_persistence.py
new file mode 100644
index 000000000..f721b5541
--- /dev/null
+++ b/tests/functional/quib/test_quib/test_quib_persistence.py
@@ -0,0 +1,24 @@
+import matplotlib.pyplot as plt
+
+from unittest import mock
+from unittest.mock import Mock
+
+import pytest
+
+from pyquibbler import iquib
+from pyquibbler.function_definitions import add_definition_for_function
+from pyquibbler.function_definitions.func_definition import create_func_definition
+from pyquibbler.quib.factory import create_quib
+from pyquibbler.quib.graphics.redraw import aggregate_redraw_mode, redraw_axeses
+from weakref import ref
+
+
+def test_quibs_deleted_when_variable_clears():
+    a = iquib(1)
+    b = a + 2
+    ref_a = ref(a)
+    ref_b = ref(b)
+    del a, b
+
+    assert ref_a() is None
+    assert ref_b() is None
diff --git a/tests/integration/benchmarks/__init__.py b/tests/integration/benchmarks/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/integration/benchmarks/benchmarks.py b/tests/integration/benchmarks/benchmarks.py
new file mode 100644
index 000000000..dbd5643f3
--- /dev/null
+++ b/tests/integration/benchmarks/benchmarks.py
@@ -0,0 +1,43 @@
+import pytest
+from pyquibbler import iquib, q
+from matplotlib import pyplot as plt
+import numpy as np
+
+
+@pytest.mark.benchmark()
+def test_speed_iquib_creation(benchmark):
+    benchmark(iquib, 1.)
+
+
+@pytest.mark.benchmark()
+def test_speed_fquib_creation(benchmark):
+    a = iquib(1.)
+    benchmark(np.sin, a)
+
+
+@pytest.mark.benchmark()
+def test_speed_get_shape(benchmark):
+    a = iquib(1.)
+    b = np.sin(a)
+    benchmark(lambda: b.get_shape())
+
+
+@pytest.mark.benchmark()
+def test_speed_get_value(benchmark):
+    a = iquib(1.)
+    b = np.sin(a)
+    benchmark(lambda: b.get_value())
+
+
+@pytest.mark.benchmark()
+def test_speed_create_graphics(benchmark, axes):
+    a = iquib(np.array([1., 2., 3., 4.]))
+    benchmark(lambda: axes.plot(a))
+
+
+@pytest.mark.benchmark()
+def test_speed_refresh_graphics(benchmark, axes):
+    a = iquib(np.array([1., 2., 3., 4.]))
+    axes.plot(a)
+    benchmark(lambda: a.assign(0.5, 1))
+
diff --git a/tests/integration/quib/graphics/test_general.py b/tests/integration/quib/graphics/test_general.py
index b0ef591bf..489c40934 100644
--- a/tests/integration/quib/graphics/test_general.py
+++ b/tests/integration/quib/graphics/test_general.py
@@ -7,7 +7,6 @@
 
 @pytest.mark.regression
 def test_graphics_function_quib_doesnt_fail_on_removal_of_artists(axes):
-    override_all()
     input_quib = iquib([1, 2, 3])
     plt.plot(input_quib)
     plt.cla()
diff --git a/tests/integration/quib/graphics/widgets/test_slider.py b/tests/integration/quib/graphics/widgets/test_slider.py
index 339838f89..41243a022 100644
--- a/tests/integration/quib/graphics/widgets/test_slider.py
+++ b/tests/integration/quib/graphics/widgets/test_slider.py
@@ -70,5 +70,5 @@ def test_slider_graphics_function_quib_calls_multiple_times(axes, get_live_widge
         create_motion_notify_event(*get_axes_end())
         create_button_release_event(*get_axes_end())
 
-    assert redraw_count.count == 4  # press * 2 + motion * 2
+    assert redraw_count.count == 6  # press * 2 + motion * 2 + release*2
     assert len(get_live_widgets()) == 1