Drawing Line chart - Out of memory problem

Carol

Member
Licensed User
Longtime User
Hi all,

My project is a program that read in 30k points and process it with FFT and high/low/band pass filter and Inverse FFT.

By using the Charing library (link below) as an example, I manage to display the 30000 point chart with horizontalscrollview and 15k point FFT with no problem.

http://www.b4x.com/forum/basic4android-getting-started-tutorials/8260-android-charts-framework.html

But when I try to reprint the FFT after performing filter, it gives me Out Of Memory problem.

After searching on the forum, some says the canvas takes too much memory to work on and they replace it with drawable bitmap. (Sorry about long intro)

Question:

1. How is drawable bitmap different from canvas
2. Other than the bitmap/canvas problem, are there more issues I need to be careful of...

The deadline is only 3 days away and hitting out of memory on Android pad is really unexpected...

appreciate of all comments

Carol
 

klaus

Expert
Licensed User
Longtime User
But when I try to reprint the FFT after performing filter, it gives me Out Of Memory problem.
Without seeing the code it's impossible to give you a concrete answer.
What are you doing when you redraw the graphic.
What size is your bitmap ?
How many bitmaps do you have ?

How is drawable bitmap different from canvas
To draw anything on a bitmap you need a canvas. The canvas bitmap property is only a pointer to the bitmap to draw on.
Depending on how you initialize the bitmaps perhaps the code in this thread to free the memory space could be useful.

Best regards.
 
Upvote 0

Carol

Member
Licensed User
Longtime User
Here is my code.

B4X:
'Activity module
Sub Process_Globals
   'These global variables will be declared once when the application starts.
   'These variables can be accessed from all modules.
   
   Type Point(X As String, Y As Double, ShowTick As Boolean)
   Type Data(Points As List, LinesColor As Int, Target As Panel, Interval As Double, YMin As Double, YMax As Double, Board As Canvas)
   
   Type DataEnvelop(Points As List)
   
   Type GraphInternal (originX As Int, originY As Int, maxY As Int, intervalX As Float, gw As Int, gh As Int)
   Type Graph (GI As GraphInternal, Title As String, YAxis As String, XAxis As String, YStart As Float, _ 
      YEnd As Float, YInterval As Float, AxisColor As Int)
   
End Sub

Sub Globals
   'These global variables will be redeclared each time the activity is created.
   'These variables can only be accessed from this module.
   
   Dim ButtonShoulder As Button
   Dim PanelShoulderI As Panel
   Dim PanelShoulderR As Panel
   
   Dim PanelShoulderIfft As Panel
   Dim PanelShoulderRfft As Panel
   
   Dim TabHostGraph As TabHost
   
   Dim HShoulder As HorizontalScrollView
   Dim VShoulder As ScrollView
   
   Dim HfftS As HorizontalScrollView
   
   Dim ShoulderI, ShoulderR As Data
   'Dim ShoulderIfft As Data
   Dim ShoulderRfft As Data
   
   Dim ShoulderREnvelopUpper  As DataEnvelop
   Dim ShoulderREnvelopLower As DataEnvelop
   
   Dim FFT As FFT
   
   'file picker
   Dim fd As FileDialog
   Dim ret As Int
   
   Dim GIfft, GRfft As Graph
   
End Sub

Sub Activity_Create(FirstTime As Boolean)

   'Load Layout
   Activity.LoadLayout("STACLayout")
   TabHostGraph.Initialize("")
   Activity.AddView(TabHostGraph, 200, 10, 1100dip, 700dip)
   
End Sub

Sub Activity_Resume

End Sub

Sub Activity_Pause (UserClosed As Boolean)

End Sub



Sub ButtonShoulder_Click

   If ButtonShoulder.Text = "Load Shoulder" Then
      PanelShoulderI.Initialize("Shoulder I Time")
      PanelShoulderR.Initialize("Shoulder R Time")
      PanelShoulderRfft.Initialize("Shoulder R FFT")
      
      HShoulder.Initialize(10000dip, "")
      
      HShoulder.Panel.AddView(PanelShoulderR, 0,0,10000dip, 300dip)
      
      HShoulder.Panel.AddView(PanelShoulderRfft, 0,305,10000dip, 300dip)
      
      TabHostGraph.AddTab2("Shoulder", HShoulder)

      HfftS.Initialize(5000dip, "")
      
      TabHostGraph.AddTab2("FFT", HfftS)
      
      '''''''''''''''
      
      ShoulderI.Initialize
      ShoulderI.Target = PanelShoulderI
      ShoulderI.LinesColor = Colors.Blue
      ShoulderR.Initialize
      ShoulderR.Target = PanelShoulderR
      ShoulderR.LinesColor = Colors.Blue
      ShoulderREnvelopUpper.Initialize
      ShoulderREnvelopLower.Initialize
      
      LoadFile("/mnt/external_sd", "Shoulder_5_18_11.txt", ShoulderI, ShoulderR, ShoulderREnvelopUpper, ShoulderREnvelopLower)
      ButtonShoulder.Text = "Graph Shoulder"
      ButtonShoulder.TextColor = Colors.Blue
   Else If ButtonShoulder.Text = "Graph Shoulder" Then
      'Dim GItime, GRtime, GIp As Graph
      Dim GRtime As Graph
      'GItime.Initialize
      GRtime.Initialize
      'InitializeGraph(GItime, "Shoulder Chart I", "Time(s)", "mmHg", ShoulderI.YMin, ShoulderI.YMax, (ShoulderI.YMax - ShoulderI.YMin)/5, Colors.Black)
      'DrawLineChart(GItime, ShoulderI, Colors.White, PanelShoulderI)
      InitializeGraph(GRtime, "Shoulder Chart R", "Time(s)", "AU",   ShoulderR.YMin, ShoulderR.YMax, (ShoulderR.YMax - ShoulderR.YMin)/5, Colors.Black)
      DrawLineChart(GRtime, ShoulderR, Colors.White, PanelShoulderR, ShoulderR.Points.Size)
      
      
      '''''''''''''''''''Calculate FFT
      
'      ShoulderIfft.Initialize
'      ShoulderIfft.Target = PanelShoulderIfft
'      ShoulderIfft.LinesColor = Colors.Green
      ShoulderRfft.Initialize
      ShoulderRfft.Target = PanelShoulderRfft
      ShoulderRfft.LinesColor = Colors.Green
      
      'CalcFFT(ShoulderI, ShoulderIfft, PanelShoulderIfft)
      CalcFFT(ShoulderR, ShoulderRfft, PanelShoulderRfft)
      
      ''''''''''''''''''Jan 09 Graph FFT
      'Dim GIfft, GRfft As Graph
      'GIfft.Initialize
      'InitializeGraph(GIfft, "Shoulder I FFT", "Hz", " ", ShoulderIfft.YMin, ShoulderIfft.YMax, (ShoulderIfft.YMax - ShoulderIfft.YMin)/5, Colors.Black)
      'DrawLineChart(GIfft, ShoulderIfft, Colors.White, PanelShoulderIfft)
      
      GRfft.Initialize
      'InitializeGraph(GRfft, "Shoulder R FFT", "Hz", " ", ShoulderRfft.YMin, ShoulderRfft.YMax, (ShoulderRfft.YMax - ShoulderRfft.YMin)/5, Colors.Black)
      InitializeGraph(GRfft, "Shoulder R FFT", "Hz", " ", ShoulderRfft.YMin, ShoulderRfft.YMax, (ShoulderRfft.YMax - ShoulderRfft.YMin)/5, Colors.Black)
      
      'InitializeGraph(GRfft, "Shoulder R FFT", "Hz", " ", ShoulderRfft.YMin, ShoulderRfft.YMax, (ShoulderRfft.YMax - ShoulderRfft.YMin)/5, Colors.Black)
      DrawLineChart(GRfft, ShoulderRfft, Colors.White, PanelShoulderRfft, ShoulderRfft.Points.Size)
      
   End If
   
End Sub

Sub CalcFFT(DataArray As Data, DataFFT As Data, Panels As Panel)
   
   
   Dim Points As Point
   'Dim TimeRealY(DataArray.Points.Size), TimeImagY(DataArray.Points.Size), FFTReal(DataArray.Points.Size/2), FFTImag(DataArray.Points.Size/2) As Double
   
   ''''''Identify the size of array and delete the size to conform with FFT rule => point size power of 2
   
   Dim FFTPointSize As Double
   'FFTPointSize = Power(2,  NumberFormat(Logarithm(DataArray.Points.Size, 2), 1, 0) - 1)
   FFTPointSize = Power(2,  NumberFormat(Logarithm(DataArray.Points.Size, 2), 1, 0))
   
   
   'Msgbox(FFTPointSize & ", " & DataArray.Points.Size, "")
   
   Dim TimeRealY(FFTPointSize), TimeImagY(FFTPointSize), FFTReal(FFTPointSize/2), FFTImag(FFTPointSize/2) As Double
   
   
   'For i=0 To DataArray.Points.Size - 1
   'For i= (DataArray.Points.Size - FFTPointSize) To DataArray.Points.Size - 1
   For i = 0 To FFTPointSize - 1
      
      If i < DataArray.Points.Size Then
         Points = DataArray.Points.Get(i)
         TimeRealY(i) = Points.Y
      Else
         TimeRealY(i) = 0
      'TimeRealY(i - (DataArray.Points.Size - FFTPointSize)) = Points.Y
      End If
   Next
   
   Dim FFTReal(TimeRealY.Length/2) As Double
   Dim FFTImag(TimeRealY.Length/2) As Double
   
   FFT.Transform2(TimeRealY, FFTReal, FFTImag)
   
   Dim Ampl(0) As Double
   Ampl = FFT.ToAmplitude(FFTReal, FFTImag)
   
   Msgbox(Ampl.Length, "")
   
   If DataFFT.IsInitialized = False Then
      DataFFT.Initialize
   End If
   'DataFFT.Target.Initialize("FFT")
   DataFFT.Points.Initialize
   
   Dim PointFFT As Point
   Dim interval As Double
   Dim MaxY, MinY As Double
   interval = DataArray.Interval
   
   
   For i=0 To Ampl.Length - 1
   
      Dim PointFFT As Point
      If i = 0 Then
         MaxY = Ampl(i)
         'MaxY = FFTReal(i)
         MinY = MaxY
      End If
      
      PointFFT.X = (i+1)/(2*interval*Ampl.Length)
      PointFFT.Y = Ampl(i)
      'PointFFT.Y = FFTReal(i)
      PointFFT.ShowTick = (i Mod 100 = 0)
      
      'If PointFFT.Y < 1.5 Then
      DataFFT.Points.Add(PointFFT)
      'End If
      
      If Ampl(i) > MaxY Then
         MaxY = Ampl(i)
      End If
      
      If MinY > Ampl(i) Then
         MinY = Ampl(i)
      End If

   Next
   
   DataFFT.YMax = MaxY
   DataFFT.YMin = MinY

End Sub

Sub BandPassFilter(FFTData As Data, UpperBound As Double, LowerBound As Double)

   ShoulderR.Points.Clear
   

   'Reset minimum and maximum value
   Dim numReset As Boolean      :numReset = False

   For i=0 To FFTData.Points.Size - 1
      Dim tmpPoint As Point
      tmpPoint = FFTData.Points.Get(i)
      
      If tmpPoint.X < LowerBound OR tmpPoint.X > UpperBound Then
         
         
         FFTData.Points.RemoveAt(i)
         tmpPoint.Y  = 0
         FFTData.Points.InsertAt(i, tmpPoint)
         
         'Msgbox(i & ": " & FFTData.Points.Size,"")
         
         If FFTData.YMin > 0 Then
            FFTData.YMin = 0
         End If
      Else
         If numReset = False Then
            FFTData.YMax = tmpPoint.Y
            FFTData.YMin = tmpPoint.Y
            numReset = True
         Else
            If tmpPoint.Y > FFTData.YMax Then
               FFTData.YMax = tmpPoint.Y
            End If
            If tmpPoint.Y < FFTData.YMin Then
               FFTData.YMin = tmpPoint.Y
            End If
         End If
      End If
   Next
   
End Sub

Sub LoadFile(FilePath As String, FileName As String, DataI As Data, DataR As Data, DataEnveUpper As DataEnvelop, DataEnveLower As DataEnvelop)
      fd.FastScroll = True
      fd.FilePath = FilePath
      fd.FileFilter=".txt"
      fd.ChosenName=FileName
      
      'Choose file
      ret = fd.Show("Choose a file to load:", "OKay", "Cancel", "", Null)
      If ret = -3 OR fd.ChosenName = "" Then
         Return
      End If
   
      If File.Exists(fd.FilePath, fd.ChosenName) = False Then
         Msgbox(fd.ChosenName & " does not exist.", "")
         Return
      Else
         
         'Identify File
         Dim FileType() As String
         
         Dim BeginOfData As Double
         
         FileType = Regex.Split("_",FileName)
         
         'Initialize Panel and DataSet
         DataI.Initialize
         DataI.Points.Initialize
         DataI.Target.Initialize(FileType & "I")
         
         DataR.Initialize
         DataR.Points.Initialize
         DataR.Target.Initialize(FileType & "R")
         
         'Determine the beginning of data index of specific data type
         If FileType(0) = "Shoulder" Then
            BeginOfData = 9
         Else
            BeginOfData = 6
         End If
         
         'Read file
         Dim ListTmp As List
         ListTmp = File.ReadList(fd.FilePath, fd.ChosenName)
         
         'Extract Interval value
         Dim Tmp1 As Matcher
         Tmp1 = Regex.Matcher("\d.\d+", ListTmp.Get(0))
               
         Do While Tmp1.Find = True
            Log(Tmp1.Match)
            DataI.Interval = Tmp1.Match
            DataR.Interval = Tmp1.Match
         Loop
         
         'Store max/min value
         Dim MaxMin(4) As Double
         
         'Store signal envelop trend
         Dim lastSign As Boolean
         Dim prevPoint As Double
         
         DataEnveUpper.Initialize
         DataEnveUpper.Points.Initialize
         DataEnveLower.Initialize
         DataEnveLower.Points.Initialize
         
         'Extract I and R data and store into DataSet
         For a = BeginOfData To ListTmp.Size - 1
            
            Log(ListTmp.Get(a))
            
            'extract data from string
            Dim Tmp3(2) As String
            Dim p,q As Point
            p.Initialize
            q.Initialize
            Tmp3 = Regex.Split("   ",ListTmp.Get(a))
            
            'Add line point
            p.X = DataI.Interval*(a - BeginOfData + 1)
            p.Y = Tmp3(0)
            p.ShowTick = ((a - BeginOfData) Mod 1000 = 0)
            DataI.Points.Add(p)
            
            q.X = DataR.Interval*(a - BeginOfData + 1)
            q.Y = Tmp3(1)
            q.ShowTick = ((a - BeginOfData) Mod 1000 = 0)
            DataR.Points.Add(q)
            
            'Store Initial Max/Min data value
            If a = BeginOfData Then
               MaxMin(0) = Tmp3(0)
               MaxMin(1) = Tmp3(0)
               MaxMin(2) = Tmp3(1)
               MaxMin(3) = Tmp3(1)
            
            Else If a = BeginOfData + 1 Then
            'check the first trend, positive if the new point is higher
               If prevPoint < Tmp3(1) Then
                  lastSign = True
               Else
                  lastSign = False
               End If
            Else
               If lastSign = True Then
                  If prevPoint > Tmp3(1) Then
                     lastSign = False
                     DataEnveUpper.Points.Add(q)
                  End If
               Else
               'vice versa
                  If prevPoint < Tmp3(1) Then
                     lastSign = True
                     DataEnveLower.Points.Add(q)
                  End If
               End If
            
               If Tmp3(0) > MaxMin(0) Then
                  MaxMin(0) = Tmp3(0)
               Else If MaxMin(1) > Tmp3(0) Then
                  MaxMin(1) = Tmp3(0)
               End If
               
               If Tmp3(1) > MaxMin(2) Then
                  MaxMin(2) = Tmp3(1)
               Else If MaxMin(3) > Tmp3(1) Then
                  MaxMin(3) = Tmp3(1)
               End If
               
            'store the last Y value of R data
               prevPoint = Tmp3(1)
            End If
         Next
            
      End If
      
      DataI.YMax = MaxMin(0)
      DataI.YMin = MaxMin(1)
      
      DataR.YMax = MaxMin(2)
      DataR.YMin = MaxMin(3)
      
End Sub

'Sub InitializeGraph(LineGraph As Graph, Title As String, XAxis As String, YAxis As String, YStart As Float, YEnd As Float , YInterval As Double, AxisColor As Int)
Sub InitializeGraph(LineGraph As Graph, Title As String, XAxis As String, YAxis As String, YStart As Double, YEnd As Double , YInterval As Double, AxisColor As Int)
   
   LineGraph.Title = Title
   LineGraph.XAxis = XAxis
   LineGraph.YAxis = YAxis
   LineGraph.YStart = YStart
   LineGraph.YEnd = YEnd
   LineGraph.YInterval = YInterval
   LineGraph.AxisColor = AxisColor
   
End Sub

Sub DrawLineChart(G As Graph, LD As Data, BackColor As Int, Panels As Panel, pointCount As Int)
   
   LD.Board.Initialize(Panels)
   LD.Board.DrawColor(BackColor)
   drawGraph(G, LD.Board, Panels, LD.Points, LD, pointCount)
   
End Sub

Sub drawGraph(G As Graph, Board As Canvas, Target As View, Points As List, LD As Data, pointCount As Int)
   
   Dim GI As GraphInternal
   GI.Initialize
   GI.gw = Target.Width - 10dip
   GI.gh = Target.Height
   GI.originX = 30dip
   GI.originY = 2dip
   
   GI.maxY = Target.Height - 5dip
   GI.intervalX = GI.gw/(pointCount - 1)
   
   'GI.intervalX = GI.gw/(points.Size - 1)
   
   'Dummy data for testing, draw a diagonal line
   'Board.DrawLine(GI.originX, GI.originY, GI.gw, GI.gh, G.AxisColor, 5dip)
   
   'Draw Y axis boarder
   Board.DrawLine(GI.originX, GI.originY + 2dip, GI.originX, GI.maxY, G.AxisColor, 2dip)
   Board.DrawLine(GI.gw,GI.originY, GI.gw, GI.maxY, G.AxisColor, 2dip)
   
   For i = 0 To 5
      Dim y As Int
      
      'Dim yValue As Float
      Dim yValue As Double
      
      'yValue = G.YStart + G.YInterval * (4 - i)
      yValue = LD.YMin + G.YInterval * (5 - i)
      
      If yValue > g.YEnd Then Continue
      
      y = GI.originY + ((GI.maxY- 5dip)/5)*i
      Board.DrawLine(GI.originX, y, GI.originX - 5dip, y, G.AxisColor, 2dip)
      Board.DrawLine(GI.originX, y, Target.Width, y, G.AxisColor, 1dip)
      Board.DrawText(NumberFormat(yValue, 1, 1), GI.originX - 5dip, y + 10dip, Typeface.DEFAULT, 12, G.AxisColor, "RIGHT")
   Next
   
   Board.DrawLine(GI.originX, GI.originY, Target.Width, GI.originY, G.AxisColor, 2dip)
   Board.DrawLine(GI.originX, GI.maxY, Target.Width, GI.maxY, G.AxisColor, 2dip)
   
   'Draw X Axis label
   'For i = 0 To Points.Size - 1
   For i = 0 To pointCount - 1
      Dim p As Point
      p = Points.Get(i)
      If p.ShowTick Then
         Dim x As Int
         x = GI.originX + i*GI.intervalX
         Board.DrawLine(x, GI.originY, x, GI.originY + 5dip, G.AxisColor, 2dip)
         Board.DrawLine(x, GI.originY, x, GI.maxY, G.AxisColor, 1dip)
         If p.X.Length > 0 Then
            Board.DrawTextRotated(NumberFormat(p.X, 0, 2), x, GI.originY + Target.Height - 30dip, Typeface.DEFAULT, 10, G.AxisColor, "RIGHT", -45)
         End If
      End If
   Next
   
   Dim dataPoint As Point
   dataPoint = LD.Points.Get(0)
   
   'Dim py As Float
   Dim py As Double
   py = dataPoint.Y
   
   Dim y1,y2 As Int
   
   'For i = 1 To LD.Points.Size - 1
   For i = 1 To pointCount - 1
      dataPoint = LD.Points.Get(i)
      
      y1 = GI.originY + (py - LD.YMax)*(GI.maxY - 5dip - GI.originY)/(LD.YMin - LD.YMax)
      y2 = GI.originY + (dataPoint.Y - LD.YMax)*(GI.maxY - 5dip - GI.originY)/(LD.YMin - LD.YMax)
         'Board.DrawLine(GI.originX + GI.intervalX*(i-1), calcPointToPixel(py, G, LD.YMax, LD.YMin), GI.originX + GI.intervalX*i, calcPointToPixel(dataPoint.Y, G, LD.YMax, LD.YMin), Colors.Blue, 2dip)
      Board.DrawLine(GI.originX + GI.intervalX*(i-1), y1, GI.originX + GI.intervalX*i, y2, Colors.Blue, 2dip)
      py = dataPoint.Y
   Next
   LD.Target.Invalidate
   Msgbox("Done Graphing", "")
   
End Sub

'Sub calcPointToPixel(py As Float, G As Graph, YMax As Float, YMin As Float) As Int
Sub calcPointToPixel(py As Float, G As Graph, YMax As Double, YMin As Double) As Int

   Return G.GI.originY + 1000*(YMax - py)*(G.GI.maxY - G.GI.originY)/(YMax - YMin) + 150dip
End Sub

Sub ButtonFilterRespiration_Click

   BandPassFilter(ShoulderRfft, 0.7, 0.1)
   InitializeGraph(GRfft, "Shoulder R FFT", "Hz", " ", ShoulderRfft.YMin, ShoulderRfft.YMax, (ShoulderRfft.YMax - ShoulderRfft.YMin)/5, Colors.Black)
   DrawLineChart(GRfft, ShoulderRfft, Colors.White, PanelShoulderRfft, ShoulderRfft.Points.Size)
   
End Sub



the data (shoulder) is included in the zipped folder and it is supposed to be read from the sd card from the android pad (Acer A500).

Please have a look and I will try the bitmap again.

thank you very much for the reply.

Carol
 
Upvote 0

klaus

Expert
Licensed User
Longtime User
I'm afraid that you panel are much too big.
10000 * 300 * 4 = 12Mbytes

In the code you posted there are at least two of them.

I suggest that you use only one panel and draw the graphic on this same panel when needed.

Best regards.
 
Last edited:
Upvote 0
Top