Repository: biolab/orange3-text

Issue:
Topic Modeling: retain settings
<!--
This is an issue template. Please fill in the relevant details in the
sections below.
-->

##### Text version
<!-- From menu _Options→Add-ons→Orange3-Text_ or code `orangecontrib.text.version.full_version` -->
0.2.4-dev

##### Orange version
<!-- From menu _Help→About→Version_ or code `Orange.version.full_version` -->
3.5.dev

##### Expected behavior
Topic Modeling remembers the selected topic and provides it on the output.


##### Actual behavior
Topic Modeling is re-run every time, thus losing the selection.


##### Steps to reproduce the behavior
Corpus - Topic Modeling. Select a topic and send it to Word Cloud. Save the workflow and reload it. Topic Modeling is re-run and not topic is sent to the output.


##### Additional info (worksheets, data, screenshots, ...)





Discussion:

Resolving patch:
diff --git a/orangecontrib/text/widgets/owtopicmodeling.py b/orangecontrib/text/widgets/owtopicmodeling.py
--- a/orangecontrib/text/widgets/owtopicmodeling.py
+++ b/orangecontrib/text/widgets/owtopicmodeling.py
@@ -130,12 +130,15 @@ class Outputs:
     hdp = settings.SettingProvider(HdpWidget)
     lda = settings.SettingProvider(LdaWidget)
 
+    selection = settings.Setting(None, schema_only=True)
+
     control_area_width = 300
 
     def __init__(self):
         super().__init__()
         self.corpus = None
         self.learning_thread = None
+        self.__pending_selection = self.selection
 
         # Commit button
         gui.auto_commit(self.buttonsArea, self, 'autocommit', 'Commit', box=False)
@@ -200,7 +203,8 @@ def apply(self):
 
     @asynchronous
     def learning_task(self):
-        return self.model.fit_transform(self.corpus.copy(), chunk_number=100, on_progress=self.on_progress)
+        return self.model.fit_transform(self.corpus.copy(), chunk_number=100,
+                                        on_progress=self.on_progress)
 
     @learning_task.on_start
     def on_start(self):
@@ -217,6 +221,9 @@ def on_result(self, corpus):
             self.Outputs.all_topics.send(None)
         else:
             self.topic_desc.show_model(self.model)
+            if self.__pending_selection:
+                self.topic_desc.select(self.__pending_selection)
+                self.__pending_selection = None
             self.Outputs.all_topics.send(self.model.get_all_topics_table())
 
     @learning_task.callback
@@ -229,8 +236,10 @@ def send_report(self):
             self.report_items('Topics', self.topic_desc.report())
 
     def send_topic_by_id(self, topic_id=None):
+        self.selection = topic_id
         if self.model.model and topic_id is not None:
-            self.Outputs.selected_topic.send(self.model.get_topics_table_by_id(topic_id))
+            self.Outputs.selected_topic.send(
+                self.model.get_topics_table_by_id(topic_id))
 
 
 class TopicViewerTreeWidgetItem(QTreeWidgetItem):
@@ -274,6 +283,7 @@ def __init__(self):
         self.resize_columns()
         self.itemSelectionChanged.connect(self.selected_topic_changed)
         self.setItemDelegate(HTMLDelegate())    # enable colors
+        self.selected_id = None
 
     def resize_columns(self):
         for i in range(self.columnCount()):
@@ -296,9 +306,8 @@ def show_model(self, topic_model):
     def selected_topic_changed(self):
         selected = self.selectedItems()
         if selected:
-            topic_id = selected[0].topic_id
-            self.setCurrentItem(self.topLevelItem(topic_id))
-            self.topicSelected.emit(topic_id)
+            self.select(selected[0].topic_id)
+            self.topicSelected.emit(self.selected_id)
         else:
             self.topicSelected.emit(None)
 
@@ -311,6 +320,10 @@ def report(self):
     def sizeHint(self):
         return QSize(700, 300)
 
+    def select(self, index):
+        self.selected_id = index
+        self.setCurrentItem(self.topLevelItem(index))
+
 
 class HTMLDelegate(QStyledItemDelegate):
     """ This delegate enables coloring of words in QTreeWidgetItem. 


Test patch:
diff --git a/orangecontrib/text/widgets/tests/test_owtopicmodeling.py b/orangecontrib/text/widgets/tests/test_owtopicmodeling.py
new file mode 100644
--- /dev/null
+++ b/orangecontrib/text/widgets/tests/test_owtopicmodeling.py
@@ -0,0 +1,59 @@
+import itertools
+import unittest
+
+import numpy as np
+from AnyQt.QtCore import QItemSelectionModel
+
+from Orange.widgets.tests.base import WidgetTest
+from orangecontrib.text.corpus import Corpus
+from orangecontrib.text.widgets.owtopicmodeling import OWTopicModeling
+
+
+class TestTopicModeling(WidgetTest):
+    @classmethod
+    def setUpClass(cls):
+        super().setUpClass()
+        cls.corpus = Corpus.from_file('deerwester')
+
+    def setUp(self):
+        self.widget = self.create_widget(OWTopicModeling)
+
+    def test_data(self):
+        def until():
+            return bool(self.get_output(self.widget.Outputs.selected_topic))
+
+        self.send_signal(self.widget.Inputs.corpus, self.corpus)
+        self.process_events(until)
+
+        self.send_signal(self.widget.Inputs.corpus, None)
+        output = self.get_output(self.widget.Outputs.selected_topic)
+        self.assertIsNone(output)
+
+    def test_saved_selection(self):
+        def until(widget=self.widget):
+            return bool(self.get_output(widget.Outputs.selected_topic,
+                                        widget=widget))
+
+        self.send_signal(self.widget.Inputs.corpus, self.corpus)
+        self.process_events(until)
+        idx = self.widget.topic_desc.model().index(2, 0)
+        self.widget.topic_desc.selectionModel().select(
+            idx, QItemSelectionModel.Rows | QItemSelectionModel.ClearAndSelect)
+        output1 = self.get_output(self.widget.Outputs.selected_topic)
+        state = self.widget.settingsHandler.pack_data(self.widget)
+
+        w = self.create_widget(OWTopicModeling, stored_settings=state)
+        self.send_signal(w.Inputs.corpus, self.corpus, widget=w)
+        self.process_events(lambda: until(w))
+        output2 = self.get_output(w.Outputs.selected_topic, widget=w)
+        # gensim uses quicksort, so sorting is unstable
+        m1 = output1.metas[output1.metas[:, 0].argsort()]
+        m2 = output2.metas[output2.metas[:, 0].argsort()]
+        # test words and weights separately, weights are not exactly equal
+        self.assertTrue((m1[:, 0] == m2[:, 0]).all())
+        np.testing.assert_allclose(m1[:, 1].astype(float),
+                                   m2[:, 1].astype(float))
+
+
+if __name__ == "__main__":
+    unittest.main()