Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Static Azimuth-Elevation heatmap #32

Open
ibaiGorordo opened this issue Mar 21, 2022 · 12 comments
Open

Static Azimuth-Elevation heatmap #32

ibaiGorordo opened this issue Mar 21, 2022 · 12 comments

Comments

@ibaiGorordo
Copy link
Owner

There seems to be a hidden mode for Azimuth-Elevation heatmap that can be enabled: #6 (comment)

It is probably a special mode for the AOP and ODS devices that when the static azimuth is selected, instead of the Range-Azimuth heatmap that was provided in previous boards, it outputs the Azimuth-Elevation heatmap (the guimonitor command is the same).

First, test in the demo visualizer with the Range-Azimuth heatmap selected. If that provides the Range-Elevation heatmap, it can probably be extracted using the same logic as the Range-Azimuth heatmap.

@Ffalzter
Copy link

My comment from the other issue

I will check you parser with my device to get the data for the static heatmaps.

@ibaiGorordo
Copy link
Owner Author

ibaiGorordo commented Mar 21, 2022

I can verify that using the configuration from the demo visualizer with the Range-Azimuth heatmap enabled, triggers the Azimuth-Elevation mode in Python (I get the TLV type with a value of 8).

However, in the demo visualizer the plot looks like the Azimuth-Range heatmap with some weird behavior, so the same parsing might not work.

@ibaiGorordo
Copy link
Owner Author

The size of the data is 24576 = (Range FFT size) x (Number of all virtual antennas) x (4Bytes), so the range fft size is 512 (number of virtual antennas is 12).

@Ffalzter
Copy link

The size of the data is 24576 = (Range FFT size) x (Number of all virtual antennas) x (4Bytes), so the range fft size is 512 (number of virtual antennas is 12).

Yeah nice.

Nevermind, I read the documentation you sent more carfully, and it says the following: NOTE: The demo will only output either the Azimuth Static Heatmap or the Azimuth/Elevation Static Heatmap

So the "Azimuth/Elevation" just means that it can be for the elevation or azimuth, but not both.

That is not 100% correct. In the documentation I sent, you can see the difference of Azimuth Static Heatmap (TLV Type 4) and Azimuth/Elevation Static Heatmap (TLV Type 8) in the Lenght. So the TLV Type 4 just uses the azimuth antennas (I think this is the same as you had it with your other sensor) and the TLV Type 8 uses all virtual antennas (= 12).
If you now look into the mmWave SDK User Guide in chapter 3.4 Configuration (.cfg) File Format and check the description of CLI command guiMonitor it says:


<rangeAzimuthHeatMap> or
<rangeAzimuthElevationHeatMap>
range-azimuth or range-azimuthelevation heat map related
information

<rangeAzimuthHeatMap>

This output is provided only in
demos that use AoA (legacy) DPU
for AoA processing
1 - enable export of zero Doppler
radar cube matrix, all range bins,
all azimuth virtual antennas to
calculate and display azimuth heat
map.

(The GUI computes the FFT of this
to show heat map)

0 - disable


< rangeAzimuthElevationHeatMap >

This output is provided in demos
that use AoA 2D DPU for AoA
processing (ex: mmW demo
for IWR6843AOP)

1 - enable export of zero Doppler
radar cube matrix, all range bins,
all virtual antennas to calculate and
display azimuth heat map.

(The GUI remaps the antenna
symbols and computes the FFT of
this stream to show azimuth heat
map only).

0 - disable

So as you said before, that you get the TLV type with a value of 8, it seems that your sensor/demo uses AoA 2D DPU for AoA processing. In the demo visualizer the elevation heatmap is not shown, as it is mentioned in the quote above.
I think, the elevation process is made using FFT on the columns.

What do you think?

@ibaiGorordo
Copy link
Owner Author

Yes, I reread it again and I realized about it. I see, makes sense, I guess you process the data the same way as the range azimuth heatmap, but at the end instead of getting a horizontal heat map, you reshape it to get a 512x4x3 cube.

@Ffalzter
Copy link

So you mean your heatmap demowith some adjustments could work?

@Ffalzter
Copy link

Ffalzter commented Mar 21, 2022

I see, makes sense, I guess you process the data the same way as the range azimuth heatmap, but at the end instead of getting a horizontal heat map, you reshape it to get a 512x4x3 cube.

Do you mean, to get the elevation heatmap you have to reshape it to 512x4x3? Because in the documentation it is described as a 2D FFT array:

This is a 2D FFT array in range direction (x[numRangeBins][numVirtualAntAzim]), at doppler index 0.

@ibaiGorordo
Copy link
Owner Author

My implementation had issues so I would not recommend it, it needs some fixes (I would say that looking at how pymmw does it will help you with that.

And for the azimuth/elevation, I would say the difference should be around the line 284 (ignore the left right flip in this case), where you will have to reshape Q to separate the virtual antennas horizontally and vertically (check the antenna order). But I might be wrong since I am not sure how the num angle bins would work in this case.

I guess it will need some trial an error, and if the demo visualizer is also processing it, if you open it in gui composer you can check at the source code to see how they do it. Or maybe ask in the TI forum.

@ibaiGorordo
Copy link
Owner Author

This is from the source code of the demo visualizer (JavaScript), it seems to be more complex than I expected:

var processAzimuthElevHeatMap = function (bytevec, byteVecIdx, Params) {
    var elapsed_time = {}; // for profile this code only
    var subFrameNum = Params.currentSubFrameNumber;
    var numTxAnt = Params.dataPath[subFrameNum].numTxAnt;
    var numRxAnt = Params.dataPath[subFrameNum].numRxAnt;

    if (subFrameNum != Params.subFrameToPlot) return;

    if (Params.guiMonitor[subFrameNum].rangeAzimuthHeatMap == 1) {
        var start_time = new Date().getTime();
        // %Range complex bins at zero Doppler all virtual antennas
        var numBytes = numTxAnt * numRxAnt * Params.dataPath[subFrameNum].numRangeBins * 4;
            
        var q = bytevec.slice(byteVecIdx, byteVecIdx + numBytes);
        // q = q(1:2:end)+q(2:2:end)*2^8;
        // q(q>32767) = q(q>32767) - 65536;
        // q = q(1:2:end)+1j*q(2:2:end);
        // ==>  q[4*idx+1]q[4*idx+0] is real, q[4*idx+3]q[4*idx+2] is imag,
        // q = reshape(q, Params.dataPath.numTxAnt*Params.dataPath.numRxAnt, Params.dataPath.numRangeBins);
        // Q = fft(q, NUM_ANGLE_BINS);  % column based NUM_ANGLE_BINS-point fft, padded with zeros
        // QQ=fftshift(abs(Q),1);
        // QQ=QQ.';
        
        var qrows ;
        /*The variable below holds the indexes of the symbols used for computing the heatmap.*/
        var rowIndexes = math.zeros(NUM_ANGLE_BINS).valueOf();
        var rowSizeBytes = numTxAnt * numRxAnt * 4;
        
        /*  Now need to select which symbols (among all virtual antennas) will
         *  be used to plot the heatmap. This info is stored in rowIndexes[]. 
         *  The Number of symbols available depends on which TX antennas are used.
         *  All other symbols are discarded.
         *  Refer to AOP AoA DPU documentation for details.
         *
         *  For xWR68xx_AOP antenna pattern, suppose that the TX antennas are transmitting in the following
         *  order: TX0 , TX1 , TX2
         *  Then, the order of the symbols received in the heatmap is 
         *  (TX0, RX0), (TX0, RX1),(TX0, RX2), (TX0, RX3),(TX1, RX0), (TX1, RX1) ..., (TX2, RX2), (TX2, RX3)
         *  If the TX antenna order is different, the received symbols order changes accordingly.
         *  The code below supports any order of TX antenna and it selects the correct symbols for heatmap 
         *  computation.
         *  Irrespective of the TX antenna order, the following symbols are used to compute the heatmap for
         *  the different antenna configurations:
         *   
         */   

		/*    num TX Ant| TX antenna(s)|  Azimuth   | Elevation | num symb used | symbols used      
         *              |              |  resolution| resolution| for heatmap   | for heatmap
		 */
		var AzimuthHeatmapAntMapping = [
			{
				/*        1     |  0           |  60 deg    | 60 deg    |      2        | (TX0, RX3), (TX0, RX1) */
				numTxAnt: 1, selectedTXAnt: [0], azRes: 60, elevRes: 60, numSymbolsHeatmap: 2, symbolArray: [{txIdx: 0, rxIdx: 3},{txIdx: 0, rxIdx: 1}]
			},
			{
				/*        2     |  0,1         |  N/A       | N/A       |      2        | (TX1, RX3), (TX1, RX1) */
				numTxAnt: 2, selectedTXAnt: [0,1], azRes: -1, elevRes: -1, numSymbolsHeatmap: 2, symbolArray: [{txIdx: 1, rxIdx: 3},{txIdx: 1, rxIdx: 1}]         
			},
			{
				/*        2     |  1,2         |  30 deg    | 60 deg    |      4        | (TX2, RX3), (TX2, RX1),(TX1, RX3), (TX1, RX1) */
				numTxAnt: 2, selectedTXAnt: [1,2], azRes: 30, elevRes: 60, numSymbolsHeatmap: 4, symbolArray: [{txIdx: 2, rxIdx: 3},{txIdx: 2, rxIdx: 1},{txIdx: 1, rxIdx: 3},{txIdx: 1, rxIdx: 1}]
			},
			{
				/*        2     |  0,2         |  60 deg    | 30 deg    |      2        | (TX2, RX3), (TX2, RX1) */
				numTxAnt: 2, selectedTXAnt: [0,2], azRes: 60, elevRes: 30, numSymbolsHeatmap: 2, symbolArray: [{txIdx: 2, rxIdx: 3},{txIdx: 2, rxIdx: 1}]         
			},
			{
				/*        3     |  0,1,2       |  30 deg    | 30 deg    |      4        | (TX2, RX3), (TX2, RX1),(TX1, RX3), (TX1, RX1) */
				numTxAnt: 3, selectedTXAnt: [0,1,2], azRes: 30, elevRes: 30, numSymbolsHeatmap: 4, symbolArray: [{txIdx: 2, rxIdx: 3},{txIdx: 2, rxIdx: 1},{txIdx: 1, rxIdx: 3},{txIdx: 1, rxIdx: 1}]
			},			
		];
		
		/* initialize more variables */
		var numRxAntEnabled = 4; /* set to max value */
        var mappingIndex=0;
		var chirpNumForTxIndx = [0, 0, 0];
			
         
        if(numTxAnt == 1)
        {
			mappingIndex=0;
			/* tx0: ch0, tx1:ch0, tx2: ch0  - special case: no matter what Tx index is, it is always on chirp0*/
			chirpNumForTxIndx = [0, 0, 0];			
        }
        else if(numTxAnt == 2)
        {
            /* There are 3 combinations of 2 antennas that can be enabled at a time
               and for each combination, we need to determine which antenna is being chirped first.
               This is what is accomplished in the code below*/
            if((Params.dataPath[subFrameNum].TxAnt0_enabled == true) && 
               (Params.dataPath[subFrameNum].TxAnt1_enabled == true))
            {		
				mappingIndex=1;
                /*Check which TX is chirping first*/
                if(Params.dataPath[subFrameNum].TxAnt0_chirpIdx < Params.dataPath[subFrameNum].TxAnt1_chirpIdx)
                {
                    //TX0 is chirping first, therefore the azimuth symbols for TX1 are coming second
					/* tx0: ch0, tx1:ch1, tx2: -1 */
					chirpNumForTxIndx = [0, 1, -1];                    
                }
                else
                {
                    //TX1 is chirping first, therefore the azimuth symbols for TX1 are coming first   
					/* tx0: ch1, tx1:ch0, tx2: -1 */
					chirpNumForTxIndx = [1, 0, -1];					           
                }                                
            }            
            else if((Params.dataPath[subFrameNum].TxAnt1_enabled == true) && 
                    (Params.dataPath[subFrameNum].TxAnt2_enabled == true))
            {
                mappingIndex=2;
                
                /*Check which TX is chirping first*/
                if(Params.dataPath[subFrameNum].TxAnt1_chirpIdx < Params.dataPath[subFrameNum].TxAnt2_chirpIdx)
                {
                    //TX1 is chirping first					
					/* tx0: -1, tx1:ch0, tx2:ch1 */
					chirpNumForTxIndx = [-1, 0, 1];
                }    
                else
                {
                    //TX2 is chirping first
					/* tx0: -1, tx1:ch1, tx2:ch0 */
					chirpNumForTxIndx = [-1, 1, 0];
                }    
            }            
            else if((Params.dataPath[subFrameNum].TxAnt0_enabled == true) && 
                    (Params.dataPath[subFrameNum].TxAnt2_enabled == true))
            {
                mappingIndex=3;
                
                /*Check which TX is chirping first*/
                if(Params.dataPath[subFrameNum].TxAnt0_chirpIdx < Params.dataPath[subFrameNum].TxAnt2_chirpIdx)
                {
                    //TX0 is chirping first
					/* tx0: ch0, tx1:-1, tx2:ch1 */
					chirpNumForTxIndx = [0, -1, 1];
                }
                else                
                {
                    //TX2 is chirping first
					/* tx0: ch1, tx1:-1, tx2:ch0 */
					chirpNumForTxIndx = [1, -1, 0];
                }
            }            
        }
        else if(numTxAnt == 3)
        {
            mappingIndex=4;
            
            /*There are 6 possible chirping order for the TX antennas*/
            if((Params.dataPath[subFrameNum].TxAnt0_chirpIdx < Params.dataPath[subFrameNum].TxAnt1_chirpIdx)&&
               (Params.dataPath[subFrameNum].TxAnt1_chirpIdx < Params.dataPath[subFrameNum].TxAnt2_chirpIdx))
            {   
                /*order is TX0->TX1->TX2*/ 
				/* tx0: ch0, tx1:ch1, tx2:ch2 */
				chirpNumForTxIndx = [0, 1, 2];
            }    
            else if((Params.dataPath[subFrameNum].TxAnt0_chirpIdx < Params.dataPath[subFrameNum].TxAnt2_chirpIdx)&&
                    (Params.dataPath[subFrameNum].TxAnt2_chirpIdx < Params.dataPath[subFrameNum].TxAnt1_chirpIdx))
            {   
                /*order is TX0->TX2->TX1*/ 
				/* tx0: ch0, tx1:ch2, tx2:ch1 */
				chirpNumForTxIndx = [0, 2, 1];
            }         
            else if((Params.dataPath[subFrameNum].TxAnt1_chirpIdx < Params.dataPath[subFrameNum].TxAnt0_chirpIdx)&&
                    (Params.dataPath[subFrameNum].TxAnt0_chirpIdx < Params.dataPath[subFrameNum].TxAnt2_chirpIdx))
            {   
                /*order is TX1->TX0->TX2*/ 
				/* tx0: ch1, tx1:ch0, tx2:ch2 */
				chirpNumForTxIndx = [1, 0, 2];
            }         
            else if((Params.dataPath[subFrameNum].TxAnt1_chirpIdx < Params.dataPath[subFrameNum].TxAnt2_chirpIdx)&&
                    (Params.dataPath[subFrameNum].TxAnt2_chirpIdx < Params.dataPath[subFrameNum].TxAnt0_chirpIdx))
            {   
                /*order is TX1->TX2->TX0*/ 
				/* tx0: ch2, tx1:ch0, tx2:ch1 */
				chirpNumForTxIndx = [2, 0, 1];
            }         
            else if((Params.dataPath[subFrameNum].TxAnt2_chirpIdx < Params.dataPath[subFrameNum].TxAnt0_chirpIdx)&&
                    (Params.dataPath[subFrameNum].TxAnt0_chirpIdx < Params.dataPath[subFrameNum].TxAnt1_chirpIdx))
            {   
                /*order is TX2->TX0->TX1*/ 
				/* tx0: ch1, tx1:ch2, tx2:ch0 */
				chirpNumForTxIndx = [1, 2, 0];
            }         
            else if((Params.dataPath[subFrameNum].TxAnt2_chirpIdx < Params.dataPath[subFrameNum].TxAnt1_chirpIdx)&&
                    (Params.dataPath[subFrameNum].TxAnt1_chirpIdx < Params.dataPath[subFrameNum].TxAnt0_chirpIdx))
            {   
                /*order is TX2->TX1->TX0*/ 
				/* tx0: ch2, tx1:ch1, tx2:ch0 */
				chirpNumForTxIndx = [2, 1, 0];
            }         

        }
		/* now create rowIndexes to pull out the symbols from heatmap */
		qrows = AzimuthHeatmapAntMapping[mappingIndex].numSymbolsHeatmap;
		for (var tmpxx = 0; tmpxx < qrows; tmpxx++) {
			rowIndexes[tmpxx] = chirpNumForTxIndx[AzimuthHeatmapAntMapping[mappingIndex].symbolArray[tmpxx].txIdx] * numRxAntEnabled +
								AzimuthHeatmapAntMapping[mappingIndex].symbolArray[tmpxx].rxIdx;				
		}
       
        var qcols = Params.dataPath[subFrameNum].numRangeBins;
        var symbIdx;
        var QQ = [];
        for (var tmpc = 0; tmpc < qcols; tmpc++) {
            var real = math.zeros(NUM_ANGLE_BINS).valueOf();
            var imag = math.zeros(NUM_ANGLE_BINS).valueOf();
            for (var tmpr = 0; tmpr < qrows; tmpr++) 
            {
                /* The indexing below was derived from the code in processAzimuthHeatMap()*/                   
                symbIdx = tmpc * rowSizeBytes + 4 * rowIndexes[tmpr];
                real[tmpr] = q[symbIdx + 1] * 256 + q[symbIdx];
                imag[tmpr] = q[symbIdx + 3] * 256 + q[symbIdx + 2];
                
                if (real[tmpr] > 32767) real[tmpr] = real[tmpr] - 65536;
                if (imag[tmpr] > 32767) imag[tmpr] = imag[tmpr] - 65536;
            }
            fft.transform(real, imag);
            for (var ri = 0; ri < NUM_ANGLE_BINS; ri++) {
                real[ri] = Math.sqrt(real[ri] * real[ri] + imag[ri] * imag[ri]); // abs()
            }
            QQ.push(real.slice(NUM_ANGLE_BINS / 2).concat(real.slice(0, NUM_ANGLE_BINS / 2)));
        }
        // QQ=QQ(:,2:end);
        // fliplr(QQ)            
        var fliplrQQ = [];
        for (var tmpr = 0; tmpr < QQ.length; tmpr++) {
            fliplrQQ.push(QQ[tmpr].slice(1).reverse());
        }
        var start_time2 = new Date().getTime();
        if (Params.rangeAzimuthHeatMapGridInit == 0) {
            // theta = asind([-NUM_ANGLE_BINS/2+1 : NUM_ANGLE_BINS/2-1]'*(2/NUM_ANGLE_BINS));
            // range = [0:Params.dataPath.numRangeBins-1] * Params.dataPath.rangeIdxToMeters;
            var theta = math.asin(math.dotMultiply(math.range(-NUM_ANGLE_BINS / 2 + 1, NUM_ANGLE_BINS / 2 - 1, true), 2 / NUM_ANGLE_BINS)).valueOf(); // in radian
            var range = math.dotMultiply(math.range(0, Params.dataPath[subFrameNum].numRangeBins - 1, true), Params.dataPath[subFrameNum].rangeIdxToMeters).valueOf();
            range = math.subtract(range, Params.compRxChanCfg.rangeBias); //correct regardless of state (measurement or compensation)
            math.forEach(range, function (value, idx, ary) {
                ary[idx] = math.max(ary[idx], 0);
            });

            // posX = range' * sind(theta');
            // posY = range' * cosd(theta');
            var posX = MyUtil.tensor(range, math.sin(theta));
            var posY = MyUtil.tensor(range, math.cos(theta));
            Params.rangeAzimuthHeatMapGrid_xlin = math.range(-range_width, range_width, 2.0 * range_width / (Params.rangeAzimuthHeatMapGrid_points - 1), true).valueOf();
            if (Params.rangeAzimuthHeatMapGrid_xlin.length < Params.rangeAzimuthHeatMapGrid_points) Params.rangeAzimuthHeatMapGrid_xlin.push(range_width);
            Params.rangeAzimuthHeatMapGrid_ylin = math.range(0, range_depth, 1.0 * range_depth / (Params.rangeAzimuthHeatMapGrid_points - 1), true).valueOf();
            if (Params.rangeAzimuthHeatMapGrid_ylin.length < Params.rangeAzimuthHeatMapGrid_points) Params.rangeAzimuthHeatMapGrid_ylin.push(range_depth);
            var xiyi = MyUtil.meshgrid(Params.rangeAzimuthHeatMapGrid_xlin, Params.rangeAzimuthHeatMapGrid_ylin);
            Params.rangeAzimuthHeatMapGrid = new math_griddata();
            Params.rangeAzimuthHeatMapGrid.init(math.flatten(posX), math.flatten(posY), xiyi[0], xiyi[1]);
            Params.rangeAzimuthHeatMapGridInit = 1;
        }
        var zi = Params.rangeAzimuthHeatMapGrid.griddata_from_cache(math.flatten(fliplrQQ));
        zi = MyUtil.reshape_rowbased(zi, Params.rangeAzimuthHeatMapGrid_ylin.length, Params.rangeAzimuthHeatMapGrid_xlin.length);
        var start_time3 = new Date().getTime();
        
        templateObj.$.ti_widget_plot4.data[0].x = Params.rangeAzimuthHeatMapGrid_xlin;
        templateObj.$.ti_widget_plot4.data[0].y = Params.rangeAzimuthHeatMapGrid_ylin;
        templateObj.$.ti_widget_plot4.data[0].z = zi;
        plotredraw(templateObj.$.ti_widget_plot4);
        
        elapsed_time.rangeAzimuthHeatMap = [start_time2 - start_time, start_time3 - start_time2, new Date().getTime() - start_time3];
    }
};

@Ffalzter
Copy link

I guess it will need some trial an error, and if the demo visualizer is also processing it, if you open it in gui composer you can check at the source code to see how they do it. Or maybe ask in the TI forum.

What do you mean with gui composer? Do I need to download the offline version of the demo visualizer?

@Ffalzter
Copy link

Ffalzter commented Mar 22, 2022

Oh wow that is a lot of code for that data process.

I asked a similar question a few days ago -> Question in TI Forum

So it seems, that there is no well-documented example how to parse the data for the heatmap.
I will have a look on the Area Scanner Lab. Maybe, like Jackson Thomas said in the thread of the TI forum from above, there is a example to parse the data.

@harithajh
Copy link

harithajh commented Jun 10, 2023

Hi I am working with IWR1642. The code is running but couldn't retrieve the data from the board. The configuration is sent and the sensor start command could be visualized but no data is retrieved. can you please help in this regard? Also 2d scatter plot opens but nothing displayed on it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants