omz:forum

    • Register
    • Login
    • Search
    • Recent
    • Popular

    Welcome!

    This is the community forum for my apps Pythonista and Editorial.

    For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.


    Putting a matplotlib.plot() image into a ui.ImageView?

    Pythonista
    4
    12
    14059
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • ccc
      ccc last edited by

      matplotlib.plot() puts an image onto the Pythonista console. I can tap and hold on that image to copy it to the clipboard, etc.

      Is there a programmatic way (image_context, etc.) to move a plot() image into a ui.ImageView?

      1 Reply Last reply Reply Quote 0
      • JonB
        JonB last edited by

        show() puts it in the console... iirc there is another option which copies an image.

        1 Reply Last reply Reply Quote 0
        • omz
          omz last edited by

          You can use savefig to save the plot to a file or file-like object. Here's an example using BytesIO:

          import matplotlib.pyplot as plt
          from io import BytesIO
          import ui
          
          plt.plot([1, 2, 3])
          
          b = BytesIO()
          plt.savefig(b)
          img = ui.Image.from_data(b.getvalue())
          
          img_view = ui.ImageView(background_color='white')
          img_view.content_mode = ui.CONTENT_SCALE_ASPECT_FIT
          img_view.image = img
          img_view.present()
          
          1 Reply Last reply Reply Quote 0
          • ccc
            ccc last edited by

            Awesome (as always). Thanks. Now, how do I make it pinchable, zoomable, scrollable?

            # coding: utf-8
            import matplotlib.pyplot as plt
            import io, math, ui
            
            # plt must be matplotlib.pyplot or its alias
            def plot_to_scrollable_image_view(plt):
                img_view = ui.ImageView()
                b = io.BytesIO()
                plt.savefig(b)
                img_view.image = ui.Image.from_data(b.getvalue())
                view = ui.ScrollView()
                view.add_subview(img_view)
                return view
            
            plt.plot([math.sin(x/10.0) for x in xrange(95)])
            plt.xlim(0, 94.2)
            view = plot_to_scrollable_image_view(plt)
            view.present()
            img_view = view.subviews[0]
            img_view.frame = view.bounds
            img_view.width *= 2
            img_view.height *= 2
            view.content_size = img_view.width, img_view.height
            

            EDIT: It is now draggable.

            1 Reply Last reply Reply Quote 0
            • ccc
              ccc last edited by

              How do I make it pinchable, zoomable?

              1 Reply Last reply Reply Quote 0
              • omz
                omz last edited by

                How do I make it pinchable, zoomable?

                There isn't really an easy way to do that, I'm afraid. Your best bet may be to save the image to a file, and then load it in a ui.WebView (a base64-encoded data: URL may also work, not sure about that right now).

                1 Reply Last reply Reply Quote 0
                • JonB
                  JonB last edited by

                  ccc, i think we really want a custom view which sets axis limits, which is relayed to plt, then a new image generated. not sure how slow that will be...

                  1 Reply Last reply Reply Quote 0
                  • Webmaster4o
                    Webmaster4o last edited by

                    @JonB I think it'd be better to PIL crop the previously-generated image.

                    1 Reply Last reply Reply Quote 0
                    • JonB
                      JonB last edited by

                      that wont be feasible if you want to zoom, otherwise you lose resolution. as an example, somone here is trying to be able to zoom into one hour from a 24 hour graph. that means you'd need to generate the original image at over 2000 dpi in order to do a simple crop with a final rsolution of 92dpi. try that in matplotlib, it is very slow, plus you then need to read and write very large files, which will be slow as well.

                      here is a proof of concept of calling matplotlib repeatedly in the ui loop.
                      The key to responsiveness is to generate very low quality images in matplotlib (say, 16 dpi) while dragging, then replace this with a higher quality version once the dragging stops.

                      I'm working on a custom touch view, which will implement zooming, two finger scrolling, to see how this scales with more complex plots.

                      # coding: utf-8
                      import matplotlib.pyplot as plt
                      import io, math, ui
                      
                      
                      def plot_to_scrollable_image_view(plt):
                          img_view = ui.ImageView()
                          b = io.BytesIO()
                          plt.savefig(b,format='png',dpi=160)
                          img_view.image = ui.Image.from_data(b.getvalue())
                          view = ui.ScrollView()
                          view.add_subview(img_view)
                          view.dx=0
                          view.ready=True
                          view.bounces=False
                          return view
                      class delegate(object):
                         #@ui.in_background  #ui.delay called from backgrounded was unreliable.
                         def scrollview_did_scroll(self,sender):
                            ui.cancel_delays()
                            d = sender.content_offset[0]-5.0
                            sender.content_offset=(5,5)
                            sender.dx=d+sender.dx
                            def hq():
                              sender.ready=False
                              dx=sender.dx
                              sender.dx=0
                              xl=plt.xlim()
                              xl=[x+dx for x in xl]
                              plt.xlim(xl)
                              b = io.BytesIO()
                              plt.savefig(b,format='jpeg',dpi=160)
                              sender.subviews[0].image = ui.Image.from_data(b.getvalue())
                              sender.ready=True
                            if sender.ready:
                              sender.ready=False
                              dx=sender.dx
                              sender.dx=0
                              xl=plt.xlim()
                              xl=[x+dx for x in xl]
                              plt.xlim(xl)
                              b = io.BytesIO()
                              plt.savefig(b,format='jpeg',dpi=16)
                              sender.subviews[0].image = ui.Image.from_data(b.getvalue())
                              sender.ready=True
                            ui.delay(hq,0.2)
                      
                              
                      plt.plot([math.sin(x/10.0) for x in xrange(95)])
                      plt.xlim(0, 94.2)
                      view = plot_to_scrollable_image_view(plt)
                      view.present()
                      view.subviews[0].frame = view.bounds
                      view.subviews[0].x,view.subviews[0].y=(5,5)
                      view.content_size=tuple(view.subviews[0].bounds.size+(11,11))
                      view.content_offset=(5,5)
                      view.delegate=delegate()
                      
                      1 Reply Last reply Reply Quote 0
                      • ccc
                        ccc last edited by

                        Sweet! I updated a few things:

                        • raised the x limit from 95 to 950 to increase the content that can be scrolled through
                        • use subplots_adjust() to reduce white space around the graph
                        • reuse hq() to remove repeated lines
                        • reduce ui.delay to zero to make refresh more snappy. Are there downsides to this?

                        I not figure out how to stop the scrolling when the graph ends (i.e. below zero or above 950)

                        I will try to apply the lessons learned to SPLnFFT_Reader.py.

                        # coding: utf-8
                        import matplotlib.pyplot as plt
                        import io, math, ui
                        
                        def plot_to_scrollable_image_view(plt):
                            img_view = ui.ImageView()
                            b = io.BytesIO()
                            plt.savefig(b, format='png', dpi=160)
                            img_view.image = ui.Image.from_data(b.getvalue())
                            view = ui.ScrollView()
                            view.add_subview(img_view)
                            view.delegate = delegate()
                            view.dx = 0
                            view.ready = True;
                            view.bounces = False
                            return view
                        
                        class delegate(object):
                           #@ui.in_background  #ui.delay called from backgrounded was unreliable.
                           def scrollview_did_scroll(self,sender):
                              ui.cancel_delays()
                              sender.dx += sender.content_offset[0] - 5.0
                              sender.content_offset = (5, 5)
                              def hq(dpi=160):
                                sender.ready=False
                                dx, sender.dx = sender.dx, 0
                                plt.xlim([x + dx for x in plt.xlim()])
                                b = io.BytesIO()
                                plt.savefig(b, format='jpeg', dpi=dpi)
                                sender.subviews[0].image = ui.Image.from_data(b.getvalue())
                                sender.ready = True
                              if sender.ready:
                                hq(16)
                              ui.delay(hq, 0)
                        
                        plt.plot([math.sin(x/10.0) for x in xrange(950)])
                        plt.xlim(0, 95)  # approx 1 cycle of the sin wave
                        plt.subplots_adjust(left=0.06, bottom=0.05, right=0.98, top=0.97)
                        view = plot_to_scrollable_image_view(plt)
                        view.hidden = True  # wait until view is setup before displaying
                        view.present()
                        img_view = view.subviews[0]
                        img_view.frame = view.bounds
                        img_view.x, img_view.y = view.content_offset = (5, 5)
                        view.content_size = tuple(img_view.bounds.size + (11, 11))
                        view.hidden = False
                        
                        1 Reply Last reply Reply Quote 0
                        • ccc
                          ccc last edited by

                          Sweet! I updated a few things:

                          • raised the x limit from 95 to 950 to give us more content to scroll through
                          • use subplots_adjust() to reduce white space around the graph
                          • reuse hq() to remove repeated lines of code
                          • reduce ui.delay to zero to make refresh more snappy. Are there downsides to doing this?

                          I could not figure out how to stop the scrolling when the graph ends (i.e. below zero or above 950)

                          I will try to apply the lessons learned to SPLnFFT_Reader.py.

                          # coding: utf-8
                          import matplotlib.pyplot as plt
                          import io, math, ui
                          
                          def plot_to_scrollable_image_view(plt):
                              img_view = ui.ImageView()
                              b = io.BytesIO()
                              plt.savefig(b, format='png', dpi=160)
                              img_view.image = ui.Image.from_data(b.getvalue())
                              view = ui.ScrollView()
                              view.add_subview(img_view)
                              view.delegate = delegate()
                              view.dx = 0
                              view.ready = True;
                              view.bounces = False
                              return view
                          
                          class delegate(object):
                             #@ui.in_background  #ui.delay called from backgrounded was unreliable.
                             def scrollview_did_scroll(self,sender):
                                ui.cancel_delays()
                                sender.dx += sender.content_offset[0] - 5.0
                                sender.content_offset = (5, 5)
                                def hq(dpi=160):
                                  sender.ready=False
                                  dx, sender.dx = sender.dx, 0
                                  plt.xlim([x + dx for x in plt.xlim()])
                                  b = io.BytesIO()
                                  plt.savefig(b, format='jpeg', dpi=dpi)
                                  sender.subviews[0].image = ui.Image.from_data(b.getvalue())
                                  sender.ready = True
                                if sender.ready:
                                  hq(16)
                                ui.delay(hq, 0)
                          
                          plt.plot([math.sin(x/10.0) for x in xrange(950)])
                          plt.xlim(0, 95)  # approx 1 cycle of the sin wave
                          plt.subplots_adjust(left=0.06, bottom=0.05, right=0.98, top=0.97)
                          view = plot_to_scrollable_image_view(plt)
                          view.hidden = True  # wait until view is setup before displaying
                          view.present()
                          img_view = view.subviews[0]
                          img_view.frame = view.bounds
                          img_view.x, img_view.y = view.content_offset = (5, 5)
                          view.content_size = tuple(img_view.bounds.size + (11, 11))
                          view.hidden = False
                          
                          1 Reply Last reply Reply Quote 0
                          • JonB
                            JonB last edited by

                            in retrospect, the whole ready checking is not needed if we dont run in the background. my original attempt had long update times, so backgrounding was neded to keep ui responsive (basicslly adding up motion, then drawing when ready).

                            it isnt too hard to have the y axis scroll control "width".
                            also, it would be possible to stop once the xlim()[0] < 0, etc
                            we also need to properly scale screen width to xlim axes, so finger motion tracks correctly.

                            howver, this approach did not scale well to a plot with 600000 points, since even a low dpi plot took many seconds to generate.
                            i think we need to actually generate a new plot each time, only plotting the portion of data within the view, and reducing the amount of data points for large plots. i.e if zoomed out, resample data to an appropriate number of points.

                            1 Reply Last reply Reply Quote 0
                            • First post
                              Last post
                            Powered by NodeBB Forums | Contributors