diff --git a/Application/learn_static.py b/Application/learn_static.py
index 6e25bc9eb6f1eb7f0f46a458374f91c2be418d62..dbadc254ce4d2864616dbb0f12963dece7e39e5e 100644
--- a/Application/learn_static.py
+++ b/Application/learn_static.py
@@ -4,9 +4,6 @@
 #except:
 #    pass
 
-import matplotlib
-matplotlib.use("Agg")
-
 from keras.layers import Dense, Dropout, Activation, Cropping2D, Flatten, Convolution1D, Convolution2D, MaxPooling1D,MaxPooling2D
 from keras.models import Sequential
 import keras
@@ -16,7 +13,6 @@ from keras.preprocessing.image import ImageDataGenerator
 from keras.utils import np_utils
 import TRex
 import utils
-import matplotlib.pyplot as plt
 
 import shutil
 
@@ -163,31 +159,6 @@ def reinitialize_network():
             metrics=['accuracy'])
     model.summary(print_fn=TRex.log)
 
-
-def plot_medians(title, train_X, train_Y, classes):
-    lengths = {}
-    f, axes = utils.axes_for_array(len(classes))
-    for ax, c in zip(axes, classes):
-        index = train_Y == c
-        ax.set_title(str(c)+": "+str(len(train_X[index])))
-        lengths[c] = len(train_X[index])
-        
-        subdata = np.copy(train_X[index])
-        
-        if len(subdata) == 0:
-            #TRex.log("skipping class "+str(c)+" because its empty")
-            continue
-        
-        img = np.max(subdata.astype(float), axis=0)[:,:,0]
-        img = np.stack((img, )*3, axis=2).astype(np.uint8)
-        ax.imshow(img)
-        
-        del subdata
-        
-    TRex.log("#samples per class: "+str(lengths))
-    #TRex.imshow(title, utils.figure_as_image())
-    #utils.show_figure(title, output_path="")
-
 class UserCancelException(Exception):
     """Raised when user clicks cancel"""
     pass
@@ -249,105 +220,11 @@ class ValidationCallback(keras.callbacks.Callback):
             
             distance = np.abs(Y - zeros).sum(axis=1)
 
-            if do_plot:
-                f, [ax, bx] = plt.subplots(1, 2, figsize=(15, 5))
-                ax.scatter(np.arange(len(distance)), distance, label="d", s = 1, c = np.ones(len(distance)), alpha = 0.5)
-                pts = np.argmax(Y, axis=1)
-                color = Y.sum(axis=1)
-
-                for j in range(len(color)):
-                    color[j] = color[j] * len(Y[j][Y[j] > 0.01])# / len(obj.classes)
-
-                pts = np.concatenate((pts, [0, ]), axis=0)
-                color = np.concatenate((color, [10, ]), axis=0)
-
-                im = bx.scatter(np.arange(len(pts)), pts, label="Y", s = 1, c = color, alpha = 0.5)
-                bx.set_yticks(np.arange(len(obj.classes_subset)))
-                bx.set_ylim(-1, len(obj.classes_subset))
-                #TRex.log(color.max(), color.min())
-                plt.colorbar(im)
-                plt.suptitle(c)
-                ax.legend()
-                bx.legend()
-                plt.show()
-                plt.close(f)
-
             result[i, 0] = np.median(np.argmax(Y, axis=1))
             result[i, 1] = distance.std()
             result[i, 2] = np.median(Y.sum(axis=1)) #distance.mean()
             result[i, 3] = (np.argmax(Y, axis=1) == i).sum() / len(Y)
         return result, predictions
-        
-    def plot_confusion_matrix(self, cm, classes,
-                                      normalize,
-                                      title,
-                                      cmap, ax = None):
-        """
-        This function prints and plots the confusion matrix.
-        Normalization can be applied by setting `normalize=True`.
-        """
-        do_show = False
-    
-        if normalize:
-            cm = cm.astype('float') / (1+cm.sum(axis=1)[:, np.newaxis])
-
-        if ax == None:
-            f, ax = plt.subplots(1,1)
-            do_show = True
-        
-        im = ax.imshow(cm, interpolation='nearest', cmap=cmap)
-        ax.set_title(title)
-        cbar = plt.colorbar(im, ax = ax)
-        cbar_ticks = np.linspace(0., 1., num=10, endpoint=True)
-        cbar.set_ticks(cbar_ticks)
-        
-        tick_marks = np.arange(len(classes))
-        ax.set_xticks(tick_marks)
-        ax.set_xticklabels([str(c) for c in classes])
-        ax.set_yticks(tick_marks)
-        ax.set_yticklabels([str(c) for c in classes])
-
-        fmt = '.2f' if normalize else 'd'
-        thresh = cm.max() / 2.
-    
-        ax.set_ylabel('True label')
-        ax.set_xlabel('Predicted label')
-        
-        #if do_show:
-        #    utils.show_figure("confusion matrix", output_path="")
-        #plt.tight_layout()
-        
-    def plot_confusion(self, X_test, Y_test, predictions = None,  title = "", ax = None, cmap = None):
-        classes = self.classes
-        #TRex.log("shapes:", X_test.shape, Y_test.shape)
-        
-        y = None
-        if type(predictions) == type(None):
-            y = self.model.predict(X_test)
-        else:
-            y = np.concatenate(predictions, axis=0)
-            assert len(y) == len(X_test)
-        
-        result = np.zeros_like(np.ndarray(shape=(len(classes),len(classes))), dtype=float)
-
-        wrong = 0
-        correct = 0
-        
-        for i in range(len(X_test)):
-            a = np.argmax(y[i,0:len(classes)])
-            b = np.argmax(Y_test[i,0:len(classes)])
-
-            if a == b:
-                correct = correct+1
-            else:
-                wrong = wrong + 1
-
-            result[a, b] = result[a, b] + 1
-            #result_x.append(a)
-            #result_y.append(b)
-
-        title = title + " (accuracy "+str(1-wrong/float(correct+wrong))+")"
-        self.plot_confusion_matrix(result, [str(classes[i]) for i in classes], normalize=True, title = title, ax = ax, cmap=cmap)
     
     def update_status(self, print_out = False, logs = {}):
         description = "epoch "+str(min(self.epoch+1, self.epochs))+"/"+str(self.epochs)
@@ -375,29 +252,11 @@ class ValidationCallback(keras.callbacks.Callback):
         
         update_work_percent(min(epoch + 1, self.epochs) / self.epochs)
         
-        #fig = plt.figure(figsize=(25,10))
-        #gs = fig.add_gridspec(2,2, width_ratios=[1,3])
-        #ax = fig.add_subplot(gs[0,0])
-        #dx = fig.add_subplot(gs[0,1])
-        #bx = fig.add_subplot(gs[1,:])
-        
         self.epoch = min(epoch + 1, self.epochs)
 
         if np.shape(self.X_test)[-1] > 0:
             result, predictions = self.plot_comparison_raw(do_plot = False, length = -1)
             
-            #dx.plot(np.arange(len(result)), result[:, 3], label="% correct")
-            #dx.scatter(np.arange(len(result)), result[:, 3])
-            #for i in np.arange(len(result)):
-            #    dx.annotate(str(classes[i])+": "+str(int(result[:, 3][i]*100))+"%", (i + 0.01, result[:, 3][i] + 0.01))
-            #dx.set_ylim(0, 1)
-            #dx.set_xticks(np.arange(len(result)))
-            #dx.set_xticklabels([str(classes[i]) for i in classes])
-            #plt.title("epoch "+str(epoch)+" range "+str(np.array(self.settings["global_segment"])))
-            #utils.show_figure("classes recognition")
-        
-            #self.plot_confusion(np.concatenate(self.X_test, axis=0), np.concatenate(self.Y_test, axis=0), predictions = predictions, ax = ax)
-
             for i in range(0, len(result[:, 3])):
                 if not i in self.per_class_accuracy:
                     self.per_class_accuracy[i] = []
@@ -406,12 +265,6 @@ class ValidationCallback(keras.callbacks.Callback):
             self.worst_values.append(np.min(result[:, 3]))
 
             set_per_class_accuracy(result[:, 3].astype(np.float))
-
-            #bx.fill_between(np.arange(len(self.worst_values)), self.worst_values, self.mean_values, where=np.array(self.worst_values) <= np.array(self.mean_values), facecolor="red", interpolate = True, alpha= 0.25)
-            
-            #bx.plot(np.arange(len(self.worst_values)), self.worst_values, label="min", color="blue")
-            #bx.plot(np.arange(len(self.mean_values)), self.mean_values, label="mean")
-
             worst_acc_per_class = np.min(result[:, 3])
         else:
             result = None
@@ -424,7 +277,6 @@ class ValidationCallback(keras.callbacks.Callback):
 
         self.uniquenesses.append(unique)
         set_uniqueness_history(self.uniquenesses)
-        #bx.plot(np.arange(len(self.uniquenesses)), self.uniquenesses, label="uniqueness", color="red")
 
         # check whether our worst value improved, but only use it if it wasnt the first epoch
         if unique > self.best_result["unique"] and (self.compare_acc <= 0 or unique >= self.compare_acc**2):
@@ -540,36 +392,7 @@ class ValidationCallback(keras.callbacks.Callback):
                         set_stop_reason("overfitting")
                         TRex.log("[STOP] overfitting. stopping with loss diffs: "+str(change))
                         self.model.stop_training = True
-        
-        #if worst_acc_per_class >= min_acceptable_value and (worst_acc_per_class >= 0.99 or (len(self.worst_values) >= min_number_iterations and self.worst_values[-1] >= min_acceptable_value)):
-         #   TRex.log("[STOP] stopping training with minimum validation accuracy of "+str(worst_acc_per_class)+" per class")
-          #  self.model.stop_training = True
-#else:
-#            TRex.log("\tcontinuing with worst accuracy of "+str(worst_value))
-        
-        #if len(self.better_values) > 0:
-        #    bx.scatter(np.array(self.better_values).T[0], np.array(self.better_values).T[1], label="improvements", color="red")
-        #    for i,j in zip(np.array(self.better_values).T[0], np.array(self.better_values).T[1]):
-        #        bx.annotate(str(int(j * 1000) / 10.0)+"%",xy=(i,j), xytext=(1,5), textcoords='offset points')
-        #if len(self.much_better_values) > 0:
-        #    bx.scatter(np.array(self.much_better_values).T[0], np.array(self.much_better_values).T[1], label="eligble", color="green")
-        
-        #if self.compare_acc > 0:
-        #    bx.axhline(y=self.compare_acc, label="previous best", color="gray", linestyle="--")
-        #    bx.axhline(y=self.compare_acc**2, label="previous accept.", color="gray", linestyle="--")
-        #bx.set_ylim(-0.05, 1.05)
-        
-        #if len(self.uniquenesses) < 20:
-        #    bx.set_xticks(np.arange(len(self.uniquenesses)))
-        #else:
-        #    bx.set_xticks(np.arange(len(self.uniquenesses), 5))
-        #bx.set_xlabel("epochs")
-        #bx.set_ylabel("accuracy")
-        #plt.legend()
-        #plt.close(plt.gcf())
-        #im = utils.figure_as_image()
-        #TRex.imshow("training overview", im)
-        #utils.show_figure(self.filename + self.prefix + " " + str(epoch) + " training overview", output_path=self.settings["output_path"], im = im)
+
         self.update_status(True, logs=logs)
         self.batches = 0
         return unique
@@ -675,7 +498,6 @@ def start_learning():
     for i, c in zip(np.arange(len(classes)), classes):
         mi = max(mi, len(Y_train[np.argmax(Y_train, axis=1) == c]))
 
-    plot_medians(filename + output_prefix + " dataset", X_train, np.argmax(Y_train, axis=1), np.array(classes, dtype=int))
     per_epoch = max(settings["min_iterations"], int(len(X_train) // batch_size ) * 0.5 ) #* 2.0) # i am using augmentation
     settings["per_epoch"] = per_epoch
     TRex.log(str(settings))
diff --git a/Application/src/tracker/CMakeLists.txt b/Application/src/tracker/CMakeLists.txt
index 536c97c79f1369443f4430d3a351f90c8dcd7255..568f659d8f931da15b4f1e1f063642cf67bd9fd6 100644
--- a/Application/src/tracker/CMakeLists.txt
+++ b/Application/src/tracker/CMakeLists.txt
@@ -122,7 +122,6 @@ set(RESOURCE_COPY_FILES
 )
 set(RESOURCE_COPY_SINGLE_FILES
 	${CMAKE_SOURCE_DIR}/default.settings
-    ${CMAKE_SOURCE_DIR}/utils.py
     ${CMAKE_SOURCE_DIR}/learn_static.py
 )
 
@@ -228,4 +227,4 @@ install(TARGETS ${targets}
 install(DIRECTORY ${CMAKE_SOURCE_DIR}/src/fonts DESTINATION ${CMAKE_INSTALL_PREFIX}/usr/share/trex)
 install(DIRECTORY ${CMAKE_SOURCE_DIR}/src/html DESTINATION ${CMAKE_INSTALL_PREFIX}/usr/share/trex)
 install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/gfx DESTINATION ${CMAKE_INSTALL_PREFIX}/usr/share/trex)
-install(FILES ${CMAKE_SOURCE_DIR}/default.settings ${CMAKE_SOURCE_DIR}/utils.py ${CMAKE_SOURCE_DIR}/learn_static.py DESTINATION ${CMAKE_INSTALL_PREFIX}/usr/share/trex)
+install(FILES ${CMAKE_SOURCE_DIR}/default.settings ${CMAKE_SOURCE_DIR}/learn_static.py DESTINATION ${CMAKE_INSTALL_PREFIX}/usr/share/trex)
diff --git a/Application/src/tracker/python/GPURecognition.cpp b/Application/src/tracker/python/GPURecognition.cpp
index 9042f2d4ea211076d532a92445e179c6b7bcfa34..db27da251f9a146cc8e9bc17d5fcdba61c4c3db1 100644
--- a/Application/src/tracker/python/GPURecognition.cpp
+++ b/Application/src/tracker/python/GPURecognition.cpp
@@ -239,7 +239,7 @@ PYBIND11_EMBEDDED_MODULE(TRex, m) {
 namespace track {
     namespace py = pybind11;
     std::shared_ptr<py::scoped_interpreter> guard = nullptr;
-    pybind11::module utils, numpy, TRex, plt, matplotlib, _main;
+    pybind11::module numpy, TRex, _main;
     pybind11::dict* _locals = nullptr;
     std::mutex module_mutex;
 
@@ -424,24 +424,7 @@ namespace track {
                              "\t\tfound = False\nprint('setting version',sys.version,found,physical)\n" \
                              "set_version(sys.version, found, physical)\n");
                 
-                matplotlib = py::module::import("matplotlib");
-                
-        //#ifdef __linux__
-                matplotlib.attr("use")("Agg");
-        //#endif
-                
-                plt = py::module::import("matplotlib.pyplot");
-                //main.attr("array") = std::vector<float>{1,2,3,4};
-                //plt.attr("plot")(main.attr("array"));
-                //plt.attr("show")();
-                
                 numpy = _main.import("numpy");
-                try {
-                    track::utils = _main.import("utils");
-                } catch(py::error_already_set &e) {
-                    Debug("Python runtime error: '%s'", e.what());
-                    e.restore();
-                }
                 TRex = _main.import("TRex");
                 
                 _locals = new py::dict("model"_a="None");
@@ -489,7 +472,7 @@ namespace track {
             }
             
             try {
-                track::utils = track::numpy = track::TRex = track::plt = track::matplotlib = _main = pybind11::module();
+                track::numpy = track::TRex = _main = pybind11::module();
 
                 {
                     std::lock_guard<std::mutex> guard(module_mutex);
diff --git a/Application/src/tracker/python/check_python.cpp b/Application/src/tracker/python/check_python.cpp
index 6b60406af55d7ba4248092ddad609e1d36a461ab..063562d7484db64f5d2cc3060bf99cf548531720 100644
--- a/Application/src/tracker/python/check_python.cpp
+++ b/Application/src/tracker/python/check_python.cpp
@@ -21,10 +21,6 @@ int main(int argc, char**argv) {
     printf("loading keras...\n");
 #endif
     importlib.attr("find_loader")("keras").attr("name");
-#ifndef NDEBUG
-    printf("loading matplotlib...\n");
-#endif
-    importlib.attr("find_loader")("matplotlib").attr("name");
 #ifndef NDEBUG
     printf("success.\n");
 #endif
diff --git a/Application/src/tracker/tracking/Accumulation.cpp b/Application/src/tracker/tracking/Accumulation.cpp
index 95f444059eb64eeea10780d71d789b8f0a70d320..eafcde5eacd78f1184ff822bd98a15b21d4374fd 100644
--- a/Application/src/tracker/tracking/Accumulation.cpp
+++ b/Application/src/tracker/tracking/Accumulation.cpp
@@ -101,7 +101,7 @@ void Accumulation::setup() {
     try {
         Recognition::check_learning_module();
     } catch(const std::future_error& error) {
-        SOFT_EXCEPTION("Checking the learning module failed ('%s'). Most likely one of the required libraries is missing from the current python environment (check for keras, tensorflow, matplotlib and pillow).", error.what());
+        SOFT_EXCEPTION("Checking the learning module failed ('%s'). Most likely one of the required libraries is missing from the current python environment (check for keras and tensorflow).", error.what());
     }
 }
 
diff --git a/Application/utils.py b/Application/utils.py
deleted file mode 100644
index f2cc3dd982a9e32e6f16c11c3f718e91221b1903..0000000000000000000000000000000000000000
--- a/Application/utils.py
+++ /dev/null
@@ -1,71 +0,0 @@
-def axes_for_array(length):
-    import numpy as np
-    import matplotlib.pyplot as plt
-
-    rest = 0
-    cols = min(16, length)
-    if length % cols > 0:
-        rest = 1
-
-    figsize = (cols * 5, 5 * (length // cols + rest))
-    fig, axes = plt.subplots(length // cols + rest, cols, figsize=figsize)
-    print(figsize)
-    
-    axes = np.array(axes).flatten()
-    for ax in axes:
-        ax.axis('off')
-    axes = axes[:length]
-
-    return fig, axes
-
-def figure_as_image():
-    import io
-    from PIL import Image
-    import matplotlib.pyplot as plt
-    import numpy as np
-    import os
-    
-    plt.gcf().set_tight_layout(True)
-    #plt.gcf().patch.set_facecolor('black')
-    plt.gcf().patch.set_alpha(0.75)
-    buf = io.BytesIO()
-    plt.savefig(buf, format='png', transparent=True, facecolor=plt.gcf().get_facecolor(), edgecolor='none')
-    buf.seek(0)
-    im = Image.open(buf)
-    im = np.array(im).astype(np.uint8)
-    buf.close()
-    plt.close(plt.gcf())
-    
-    return im
-
-def show_figure(title="plot", output_path="", im = None):
-    import TRex
-    from PIL import Image
-    import numpy as np
-    import os
-
-    if type(im) == type(None):
-        im = Image.fromarray(figure_as_image())
-    else:
-        im = Image.fromarray(im)
-    try:
-        path = "/var/www/example.com/html/"+title.replace(" ", "_").replace("/", "-")+".png"
-        im.save(path, "PNG")
-        TRex.log("saved as"+str(path))
-    except Exception as e:
-        TRex.warn(str(e))
-
-    if len(output_path) > 0:
-        try:
-            if not output_path.endswith(os.sep):
-                output_path = output_path + os.sep
-            path = output_path+title.replace(" ", "_").replace("/", "-")+".png"
-            im.save(path, "PNG")
-            TRex.log("saved as"+str(path))
-        except Exception as e:
-            TRex.warn(str(e))
-    
-    im = np.array(im).astype(np.uint8)
-
-    #TRex.imshow(title, im)
-    
diff --git a/conda/meta.yaml b/conda/meta.yaml
index f8e1a2ae63ff3e54f744d960ea353220e98ec0ef..1afefd101d12ce2668ed621f3670e016e59d2ca3 100644
--- a/conda/meta.yaml
+++ b/conda/meta.yaml
@@ -67,8 +67,6 @@ requirements:
     - tensorflow-gpu ==1.13.* [not osx]
     - tensorflow ==1.13.* [osx]
     - keras
-    - matplotlib
-    - pillow
     - ffmpeg [win]
     - python [win]
     - ffmpeg ==4.0 [not win]