Solved

How to save detector image as 24 bit, uint8, rbg format when running ZEMAX via MATLAB


Hi, I am running some simulations in ZEMAX via MATLAB for image analysis after changing some parameters. I am running in non-sequential mode and detector type rectangle below. 


My issue is getting the wrong image format via MATLAB. The detector image I save manually in detector viewer in ZEMAX has format is a 800x800x8 uint8 with 24 bit image. This is what I want to use for further analysis in MATLAB, but its cumbersum to manually save each image for each simulation when you do many. I just want to run this in the background and save time. Thus, I want to save the detector image (suppress frame) after each ray trace after changing some parameters with a for loop. 


I found a way to run the simulation in MATLAB, but extracting the same image has been difficult. 


I extract the detector image from ZEMAX via the code; 


% Get detector data

    data = NET.createArray('System.Double', TheNCE.GetDetectorSize(30));

    TheNCE.GetAllDetectorData(30, 1, TheNCE.GetDetectorSize(30), data);

    

    [~, rows, cols] = TheNCE.GetDetectorDimensions(30);

    % rotates and flips image to reflect ZOS orientation

    detectorData = flipud(rot90(reshape(data.double, rows, cols)));


   imshow(detectorData,[]);

   imwrite(detectorData,'fileNameTmp.jpg');


but the parameter detectorData here is a 800x800 matrix and not in the format I want. See below of the image information from the two methods .


My question is, can i save the detector image from my MATLAB (without the white frame around) in the same image format ZEMAX saves when ray-tracing it manually from ZEMAX? I want the image to be truecolor and in 24 bit jpg. 


Many thanks,













ZEMAX via MATLAB, 
           FileSize: 94982

             Format: 'jpg'

      FormatVersion: ''

              Width: 800

             Height: 800

           BitDepth: 24

          ColorType: 'truecolor'

    FormatSignature: ''

    NumberOfSamples: 3

       CodingMethod: 'Huffman'

      CodingProcess: 'Sequential'

            Comment: {}
           FileSize: 62444

             Format: 'jpg'

      FormatVersion: ''

              Width: 800

             Height: 800

           BitDepth: 8

          ColorType: 'grayscale'

    FormatSignature: ''

    NumberOfSamples: 1

       CodingMethod: 'Huffman'

      CodingProcess: 'Sequential'

            Comment: {}

 

icon

Best answer by David.Nguyen 20 April 2021, 22:23

View original

13 replies

Userlevel 7
Badge +2

Hi Susan,


Have a look at Example 25 from the ZOS-API Syntax Help. You can find it from the OpticStudio interface by clicking Programming...ZOS-API Help...ZOS-API Syntax Help. In the Help document, which just opened, click on the Examples menu, and select Example 25 > MATLAB.


This is a rather complex example, but the interesting bit is how you setup your Detector Color after you've run a raytrace:



% Creates a new detector viewer window, changes to true color
det = TheSystem.Analyses.New_Analysis(ZOSAPI.Analysis.AnalysisIDM.DetectorViewer);
det_settings = det.GetSettings();
det_settings.ShowAs = ZOSAPI.Analysis.DetectorViewerShowAsTypes.TrueColor;
det.ApplyAndWaitForCompletion();

And how you can read its RGB values:



% Creates System.Single[] buffers to store pixel data
rData = NET.createArray('System.Single', (x_pixel * y_pixel));
gData = NET.createArray('System.Single', (x_pixel * y_pixel));
bData = NET.createArray('System.Single', (x_pixel * y_pixel));
% Loads RGB data into System.Single buffer
det_raw = det.GetResults().DataGridsRgb.Get(0);
det_raw.FillValues((x_pixel * y_pixel), rData, gData, bData);
% Converts buffer to RGB array; rotates & resizes RGB array
dData = zeros(y_pixel, x_pixel, 3) - 1;
dData(:,:,1) = rot90(reshape(rData.double ./ 255, x_pixel, y_pixel));
dData(:,:,2) = rot90(reshape(gData.double ./ 255, x_pixel, y_pixel));
dData(:,:,3) = rot90(reshape(bData.double ./ 255, x_pixel, y_pixel));

It calls for the GetResults().DataGridsRgb.Get(0) method, and not the GetAllDetectorData() method you used, which I think is confined to monochromatic data.


Let me know if this helps as I didn't try it myself with MATLAB (I don't own a licence at the moment).


PS: from what I know, the ZOS-API will retrieve your data in a Double-format with 64-bits precision in each color channel, and store it into a 'double' 3D MATLAB matrix (third dimension is RGB). It is up to you to downscale it to 8-bits before saving (let me know if you need help with that).


Take care,


David

I got a bit uncertain now. The question is if the images are rgb or not. I think i confused the format they are saved as. The images in ZEMAX looks black/gray/white. So I don't want the images in blue/green/yellow scale etc.. 


Do you think your code example above would be able to save those as gray-scale as well. Yes, checking i see that I have the Inverse Gray Scale as shown as in ZEMAX, so the images I save manually there are saved like that and later in MATLAB. 


I have to check your code next week. 


 

Userlevel 7
Badge +2

Hi Susan,


The image is stored as a 3D matrix in MATLAB, the first two dimensions are the pixels of your image, and the third one is the color channel. Index 0 is the red component, 1 is the green component, and 2 is the blue component.


You can use this matrix to save it as an rgb-jpeg, but you can also take the mean value along the third dimension to save a grayscale image. Does this make sense?


Let me know once you had a chance to look at the code, which is not from me by the way. Its an OpticStudio built-in example.


Take care,


David

Ok, I tried a bit before heading home.


I get error at:


        % Loads RGB data into System.Single buffer

        det_raw = det.GetResults().DataGridsRgb.Get(0);

MATLAB says;


Error using Analysis_infinity_v2>BeginApplication (line 130)

Attempting to access the property or method of an invalid object.

Error in Analysis_infinity_v2 (line 14)

        r = BeginApplication(TheApplication, args);

My detector is at row 30.

Should I not define that somewhere?

I am comfused why it is zero at .Get(0).

I also tied with .Get(30). Same issue. 


My MATLAB code: 


Some initial code here ..



TheSystem.LoadFile(testFile,true);

% Initializes NCE and loads surfaces

% Non-sequential component editor

% Create result file

writeResultToFileTitle(resultDir);

xfoc = 75; % focallengths

for zPos = [100, 150, 200, 250, 300, 350, 400, 500, 600, 700, 800, 1200, 2000, 3000,4000]

    TheAnalyses = TheSystem.Analyses;

    % Non-sequential component editor

    TheNCE = TheSystem.NCE;

    

    % change eye distace

    o26 = TheNCE.GetObjectAt(26); % rad 26

    % Moves the field stop infront of the Retina

    writeZPosition(o26, zPos);

    

    TheSystem.Save;

    

    % Set focus

    o30 = TheNCE.GetObjectAt(30); % rad 30

    % Moves the field stop infront of the Retina writeZPosition(o30, xfoc);

    

    % saves file to disk to expose all objects

    TheSystem.Save;

    

    % Run Ray Trace

    runRayTrace(TheSystem);

    

    % o30 = TheNCE.GetObjectAt(30);

    % Do anlysis of the detector (30) Retina

    o30_results = doRayTraceAnalysis(TheAnalyses, 30);

    

    % Get the total power and plot the intensity

    o30_totalPower = getTotalDetectorPower(o30_results, resultDir, zPos);



  

    % Creates a new detector viewer window, changes to true color

    det = TheSystem.Analyses.New_Analysis(ZOSAPI.Analysis.AnalysisIDM.DetectorViewer);

    det_settings = det.GetSettings();

    det_settings.ShowAs = ZOSAPI.Analysis.DetectorViewerShowAsTypes.TrueColor;

    det.ApplyAndWaitForCompletion();

    

    % Setup detector color

    x_width = 6;

    y_width = 6;

    x_pixel = 800;

    y_pixel = 800;

    

    close all;

    figure('OuterPosition',[0, 250, 1000, 500])

%     for a = 1:TheMCE.NumberOfConfigurations

%        TheMCE.SetCurrentConfiguration(a);

        a = 1;

%         % Setup and run the ray trace

%         NSCRayTrace = TheSystem.Tools.OpenNSCRayTrace();

%         NSCRayTrace.ClearDetectors(0);

%         NSCRayTrace.SplitNSCRays = false;

%         NSCRayTrace.ScatterNSCRays = false;

%         NSCRayTrace.UsePolarization = false;

%         NSCRayTrace.IgnoreErrors = true;

%         NSCRayTrace.SaveRays = false;

%         NSCRayTrace.RunAndWaitForCompletion();

%         NSCRayTrace.Close();

%         fprintf('Finished ray trace\n');

%         

        %! [e25s06_m]

        % Creates a new detector viewer window, changes to true color

        det = TheSystem.Analyses.New_Analysis(ZOSAPI.Analysis.AnalysisIDM.DetectorViewer);

        det_settings = det.GetSettings();

        det_settings.ShowAs = ZOSAPI.Analysis.DetectorViewerShowAsTypes.TrueColor;

        det.ApplyAndWaitForCompletion();

        %! [e25s06_m]

        

        % Creates System.Single[] buffers to store pixel data

        rData = NET.createArray('System.Single', (x_pixel * y_pixel));

        gData = NET.createArray('System.Single', (x_pixel * y_pixel));

        bData = NET.createArray('System.Single', (x_pixel * y_pixel));

        

        %! [e25s07_m]

        % Loads RGB data into System.Single buffer

        det_raw = det.GetResults().DataGridsRgb.Get(30);

        det_raw.FillValues((x_pixel * y_pixel), rData, gData, bData);

        %! [e25s07_m]

        

        % Converts buffer to RGB array; rotates & resizes RGB array

        dData = zeros(y_pixel, x_pixel, 3) - 1;

        dData(:,:,1) = rot90(reshape(rData.double ./ 255, x_pixel, y_pixel));

        dData(:,:,2) = rot90(reshape(gData.double ./ 255, x_pixel, y_pixel));

        dData(:,:,3) = rot90(reshape(bData.double ./ 255, x_pixel, y_pixel));

        

        % Plots detector color values

        subplot(1, double(TheMCE.NumberOfConfigurations), double(a))

        imagesc(dData,'X',[-x_width x_width],'Y',[-y_width y_width]);

        axis equal tight;colormap('jet');

%         str = sprintf('Config = %i');

%         title(str);



    % Save and Close

    TheAnalyses.CloseAllAnalyses;

    TheSystem.SaveAs(testFile);
Userlevel 7
Badge +2

Hi Susan,


You do not need to specify where your detector is if you know the pixel size (to allocate the memory for the MATLAB matrix). Let me explain, what you do with this line:



TheSystem.Analyses.New_Analysis(ZOSAPI.Analysis.AnalysisIDM.DetectorViewer)

Is you effectively create and open a new Detector Viewer analysis window, and the Detector represented in the Detector Viewer is a setting of the Detector Viewer itself. You can also see this directly in OpticStudio, in a Detector Viewer window, you can press Settings...Detector, and see what Detector it picks its data from. If you have only one detector, it will use this one by default. If you have more than a single detector, it'll pickup the first one in the non-sequential data editor. It is not shown in this example, but you can change the Detector programatically with this line:



det_settings.Detector.SetDetectorNumber(30)

Which you would put just after the line where you retrieve the analysis settings:



det_settings = det.GetSettings()

In Example 25 though, they have their Detector Color in Object 5 (called o5 in the code). And they use it to setup the dimension of the detector in pixels. However, I don't think this is relevant for you, so you might want to ignore it at first.


You should not use:



det.GetResults().GetDataGridRgb(30)

Because, the integer parameter is not the detector number. This parameter correspond to the index of the data grid you are looking for. Most of the time, it'll be 0, because those analysis only return a single data grid, and in most langages (except MATLAB), the first index is zero.


You can check how many data grids are available with this line of code:



det.GetResults().NumberOfDataGridsRgb

If this returns anything other than 1 (based on your error, it probably returns 0), I'd suspect you might not have the right Detector in the settings. In which case, you can use the code I've proposed to change it to your Detector Color, perhaps something like:



# creates a new detector viewer window, changes to true color
det = TheSystem.Analyses.New_Analysis(ZOSAPI.Analysis.AnalysisIDM.DetectorViewer)
det_settings = det.GetSettings()
# ensure detector viewer is true color to extract RGB data
det_settings.ShowAs = ZOSAPI.Analysis.DetectorViewerShowAsTypes.TrueColor
# specify detector color position
det_settings.Detector.SetDetectorNumber(30)
det.ApplyAndWaitForCompletion()

Let me know if this helps,


David

Userlevel 7
Badge +3

A potentially easier way is to simply use SAVEDETECTORDATA from the UI, ZPL or ZOS-API and then simply LOADDETECTORDATA and EXPORTGRAPHIC to get the graphic in the format you want. In fact, you could load the detector data into a Matlab array directly.

Ah, I see... problem is that I don't know how to use SAVEDETECTORDATA from the UI, ZPL or ZOS-AP.

I am totally a newbie to ZEMAX. 

So, it return 0 .. but I guess 




det_settings.Detector.SetDetectorNumber(30)


is not the row 30. 


I have in total 12 detectors in the file. 


I still get error 'Attempting to access the property or method of an invalid object.' at 


        det.GetResults().NumberOfDataGridsRgb

        det_raw = det.GetResults().DataGridsRgb.Get(0);

used the code


    % creates a new detector viewer window, changes to true color

    det = TheSystem.Analyses.New_Analysis(ZOSAPI.Analysis.AnalysisIDM.DetectorViewer)

    det_settings = det.GetSettings()

    det_settings.Detector.SetDetectorNumber(9) % out pf 12 detectors in the ZEMAX file

    % ensure detector viewer is true color to extract RGB data

    det_settings.ShowAs = ZOSAPI.Analysis.DetectorViewerShowAsTypes.TrueColor

    % specify detector color position

    det.ApplyAndWaitForCompletion()

 I am not sure what the problem is?


the det_setting has the following properties



det_settings = 


  AS_DetectorViewer with properties:


                          Surface: [1×1 ZOSAPI.Analysis.AS_Surface]

                         Detector: [1×1 ZOSAPI.Analysis.AS_Detector]

                           ShowAs: TrueColor

                            Scale: Linear

                         DataType: PositionSpace

                       SymbolType: 0

                    Configuration: 1

                    ExtraProperty: 0

                        Smoothing: 0

                           RowCol: 0

                           Zplane: 0

              MaxSpatialFrequency: 0

                         Contrast: 0

                        AngleList: [1×1 System.UInt16[]]

                 PlotScaleMinimum: 0

                 PlotScaleMaximum: 0

              RayDatabaseFilename: [1×1 System.String]

                           Filter: [1×1 System.String]

                          OutFile: [1×1 System.String]

                    SuppressFrame: 1

            NonsequentialSurfaces: [1×1 System.Collections.Generic.List>]

               DetectorsOnSurface: [1×1 System.Collections.Generic.List>]

    NumberOfNonSequentialSurfaces: 1

       NumberOfDetectorsOnSurface: 12

              NumberOfShowAsTypes: 6

                         Analysis: [1×1 ZemaxUI.ZOSAPI.Analysis.ZemaxAnalyses]

                        TheDataID: [1×1 ZOSAPI.Analysis.DataID]

                          IsValid: 1

                   IsActiveWindow: 1

                           Parent: [1×1 ZemaxUI.ZOSAPI.Analysis.RayTracing.A_DetectorViewer]



 

I now tried removing all the other detectors and just keeping the one I want to plot,



det_settings = 


  AS_DetectorViewer with properties:


                          Surface: [1×1 ZOSAPI.Analysis.AS_Surface]

                         Detector: [1×1 ZOSAPI.Analysis.AS_Detector]

                           ShowAs: TrueColor

                            Scale: Linear

                         DataType: PositionSpace

                       SymbolType: 0

                    Configuration: 1

                    ExtraProperty: 0

                        Smoothing: 0

                           RowCol: 0

                           Zplane: 0

              MaxSpatialFrequency: 0

                         Contrast: 0

                        AngleList: [1×1 System.UInt16[]]

                 PlotScaleMinimum: 0

                 PlotScaleMaximum: 0

              RayDatabaseFilename: [1×1 System.String]

                           Filter: [1×1 System.String]

                          OutFile: [1×1 System.String]

                    SuppressFrame: 1

            NonsequentialSurfaces: [1×1 System.Collections.Generic.List>]

               DetectorsOnSurface: [1×1 System.Collections.Generic.List>]

    NumberOfNonSequentialSurfaces: 1

       NumberOfDetectorsOnSurface: 1

              NumberOfShowAsTypes: 6

                         Analysis: [1×1 ZemaxUI.ZOSAPI.Analysis.ZemaxAnalyses]

                        TheDataID: [1×1 ZOSAPI.Analysis.DataID]

                          IsValid: 1

                   IsActiveWindow: 1

                           Parent: [1×1 ZemaxUI.ZOSAPI.Analysis.RayTracing.A_DetectorViewer]



 with code changes


%         

    % creates a new detector viewer window, changes to true color

    det = TheSystem.Analyses.New_Analysis(ZOSAPI.Analysis.AnalysisIDM.DetectorViewer)

    det_settings = det.GetSettings()

    det_settings.Detector.SetDetectorNumber(1) % detector 1 at row 22 , also tried (0)

    % ensure detector viewer is true color to extract RGB data

    det_settings.ShowAs = ZOSAPI.Analysis.DetectorViewerShowAsTypes.GreyScale_Inverted

    % specify detector color position

    det.ApplyAndWaitForCompletion()



        % Loads RGB data into System.Single buffer

        det.GetResults().NumberOfDataGridsRgb

        det_raw = det.GetResults().DataGridsRgb.Get(0);

        det_raw.FillValues((x_pixel * y_pixel), rData, gData, bData);

 still get error 'Attempting to access the property or method of an invalid object.' at 'det_raw = det.GetResults().DataGridsRgb.Get(0);'


Cannot figure out what the problem is. 


Notice that i changed truecolor to Gray Scale Inverted since this is the one I use to plot the image. Not sure what to use as det.GetResults().DataGridsRgb.Get(0);' DataGrid when using show as 'gray inverted'. 


 


I tried 


        det_raw = det.GetResults().DataGrids.Get(0);

 and that worked but then i got error at line


det_raw.FillValues((x_pixel * y_pixel), rData, gData, bData);

Error says then, No appropriate method, property, or field 'FillValues' for class 'ZemaxUI.ZOSAPI.Analysis.AR_DataGrid'.

 

Ok, I got a little further. realised that rpg is 3 dimensional matrix. I think inverse_gray is only 1 dim. 



    % disp('get image');

    tic

  

    % Setup detector size

    x_width = 6;

    y_width = 6;

    x_pixel = 800;

    y_pixel = 800;

    

    close all;

    figure('OuterPosition',[0, 250, 1000, 500])

%     for a = 1:TheMCE.NumberOfConfigurations

%        TheMCE.SetCurrentConfiguration(a);

        a = 1

%         

    % creates a new detector viewer window, changes to true color

    det = TheSystem.Analyses.New_Analysis(ZOSAPI.Analysis.AnalysisIDM.DetectorViewer)

    det_settings = det.GetSettings()

   % det_settings.Detector.SetDetectorNumber(0) % detector 1 at row 22

    % ensure detector viewer is true color to extract RGB data

    det_settings.ShowAs = ZOSAPI.Analysis.DetectorViewerShowAsTypes.GreyScale_Inverted

    % specify detector color position

    det.ApplyAndWaitForCompletion()

    

        

        % Creates System.Single[] buffers to store pixel data

        rData = NET.createArray('System.Single', (x_pixel * y_pixel));


        % Loads RGB data into System.Single buffer

        %det.GetResults().NumberOfDataGridsRgb

        %det_raw = det.GetResults().DataGridsRgb.Get(0);

        det_raw = det.GetResults().DataGrids.Get(0);

       

        det_raw.FillValues((x_pixel * y_pixel), rData);


        % Converts buffer to RGB array; rotates & resizes RGB array

        dData = zeros(y_pixel, x_pixel, 1) - 1;

        dData(:,:,1) = rot90(reshape(rData.double ./ 255, x_pixel, y_pixel));

        

        % Plots detector color values

        subplot(1, double(TheMCE.NumberOfConfigurations), double(a))

        imagesc(dData,'X',[-x_width x_width],'Y',[-y_width y_width]);

        axis equal tight;colormap('jet');

%         str = sprintf('Config = %i');

%         title(str);



still get error at line 'subplot(1, double(TheMCE.NumberOfConfigurations), double(a))', errror says


 No appropriate method, property, or field 'FillValues' for class 'ZemaxUI.ZOSAPI.Analysis.AR_DataGrid'.


Any ideas what to do here?

Userlevel 7
Badge +2

Hi Susan,


I was a bit confused at your answers. I re-read the whole conversation, and here's what I think:




  1. Detector.SetDetectorNumber(30) expects the Object Number as specified in the non-sequential editor. If your detector is in line 30, it should be 30. The system doesn't know how many detectors you have




  2. This methods works for a Detector Color, it won't work for any other kind of detector




The reason I gave you this method is because you asked for a 'True Color' image, and the Detector Color is the only I know which can do that.


At this point, can I ask you to share your code, an example file, and an explanation as to what you are trying to achieve?


As I said, I don't own a MATLAB licence, and all I can do is try to guess what is going wrong. Regarding your last reply, I saw that you kept the part from the example about the Multi-Configuration Editor. If you have done so, you might want to check that it is setup accordingly in your lens design file, or if you don't have multi-configuration enabled, perhaps you might want to check that this part of the code is not interfering.


Take care,


David

Hi, a little update. I finally managed to solve the issue.

I am basically extracting the data, pixel by pixel, from the detector as inverted grayscale. 


Thanks for the help.


My solution: 


Set your detector to show as,.. in my case invested gray-scale or whatever u want. Just notice that the solution depends on the how the detector is to shown as. For example a color map gives the result in 3D matrix represented as red, green, blue and the first suggestion in this thread, in example 25 of the help section, should work well. In my case, the image is gray-scale which is a 1D matrix. 


 det_settings.ShowAs = ZOSAPI.Analysis.DetectorViewerShowAsTypes.GreyScale_Inverted;

Create a parameter to store your detector raw Data in and extract it. Before this I also pick which detector I want to extract data from. 


det_settings.Detector.SetDetectorNumber(30); % pick detector, 30 here is the row no. in editor.

det_raw = det.GetResults(); 

dataGray = det_raw.DataGrids.Get(0);

and then extract data in a for-loop 


imageM(i,j) = dataGray.ValueData.GetValueAt(i,j);

Thats it.  

Userlevel 7
Badge +2

Hi Susan,


I'm glad to hear you are sorted. I hope this discussion helped you.


Don't hesitate to share your solution as well, if you think it can be helpful to someone else.


Take care,


David

Reply