Image Fitting ------------- .. uml:: skinparam style strictuml hide footbox title Image Fitting workflow actor User box "Client-side" #EDEDED participant Frontend end box box "Server-side" #lightblue participant Backend end box User -> Frontend: Open image activate Frontend Frontend -> Backend : 1. OPEN_FILE activate Backend Frontend <-- Backend : 2. OPEN_FILE_ACK Frontend -> Backend : 3. ADD_REQUIRED_TILES Frontend -> Backend : 4. SET_CURSOR Frontend <-- Backend : 5. RASTER_TILE_DATA Frontend <-- Backend : 5. SPATIAL_PROFILE_DATA deactivate Backend User <-- Frontend: Displays image deactivate Frontend User -> Frontend: Request Gaussian fitting activate Frontend Frontend -> Backend : 6. FITTING_REQUEST activate Backend Frontend <-- Backend : 7. FITTING_PROGRESS (stream) Frontend <--[#red] Backend : 8. FITTING_RESPONSE [Check] deactivate Backend User <-- Frontend: Displays fitting results deactivate Frontend IMAGE_FITTING_FITS ~~~~~~~~~~~~~~~~~~ See the `source code `__. This test verifies Gaussian image fitting on FITS files, covering various configurations: without field of view (FoV), with FoV using different solvers (Cholesky, QR, SVD), creating model and residual images, region-based fitting, and fitting with background flux offset. 1. Frontend sends: **CLOSE_FILE** (``CloseFile``) .. code-block:: protobuf file_id = -1 2. Frontend sends: **OPEN_FILE** (``OpenFile``) .. code-block:: protobuf directory = "set_QA" file = "M17_SWex-channel0-addOneGaussian.fits" hdu = "0" file_id = 0 render_mode = RASTER 3. Backend returns: **OPEN_FILE_ACK** (``OpenFileAck``) and **REGION_HISTOGRAM_DATA** :red-text:`Check 1:` the OPEN_FILE_ACK should satisfy: - OPEN_FILE_ACK.success = True 4. Frontend sends: **ADD_REQUIRED_TILES** (``AddRequiredTiles``) .. code-block:: protobuf file_id = 0 compression_quality = 11 compression_type = ZFP tiles = [0] 5. Frontend sends: **SET_CURSOR** (``SetCursor``) .. code-block:: protobuf file_id = 0 point = {x: 1, y: 1} 6. Backend returns: **RASTER_TILE_DATA** and **SPATIAL_PROFILE_DATA** :red-text:`Check 2:` the RASTER_TILE_DATA stream should satisfy: - Total length = 3 (RasterTileSync start + 1 tile + RasterTileSync end) **Case 1: Image fitting without FoV** 7. Frontend sends: **FITTING_REQUEST** (``FittingRequest``) .. code-block:: protobuf file_id = 0 create_model_image = false create_residual_image = false fixed_params = [false, false, false, false, false, false, true] fov_info = null region_id = -1 initial_values = [{amp: 10, center: {x: 320, y: 400}, fwhm: {x: 100, y: 50}, pa: 135}] 8. Backend returns: **FITTING_PROGRESS** (stream) and **FITTING_RESPONSE** (``FittingResponse``) :red-text:`Check 3:` the FITTING_RESPONSE should satisfy: .. code-block:: protobuf success = true result_values[0].center = {x: 319.50, y: 399.50} result_values[0].amp = 10.00 result_values[0].fwhm = {x: 170.64, y: 41.48} result_values[0].pa = 142.16 log contains "Gaussian fitting with 1 component" - resultErrors should be close to zero **Case 2-1: Image fitting with FoV (solver = Cholesky)** 9. Frontend sends: **FITTING_REQUEST** (``FittingRequest``) .. code-block:: protobuf file_id = 0 create_model_image = false create_residual_image = false fixed_params = [false, false, false, false, false, false, true] fov_info = {control_points: [{x: 319.5, y: 399.5}, {x: 216.71, y: 200.0}], region_type: RECTANGLE, rotation: 0} region_id = 0 initial_values = [{amp: 10, center: {x: 320, y: 400}, fwhm: {x: 100, y: 50}, pa: 135}] solver = Cholesky 10. Backend returns: **FITTING_RESPONSE** (``FittingResponse``) :red-text:`Check 4:` the FITTING_RESPONSE should satisfy: .. code-block:: protobuf success = true result_values[0].center = {x: 319.50, y: 399.50} result_values[0].amp = 10.00 result_values[0].fwhm = {x: 170.64, y: 41.48} result_values[0].pa = 142.16 **Case 2-2: Image fitting with FoV (solver = QR)** 11. Frontend sends: **FITTING_REQUEST** with solver = QR 12. Backend returns: **FITTING_RESPONSE** (``FittingResponse``) :red-text:`Check 5:` the FITTING_RESPONSE should match Case 2-1 results **Case 2-3: Image fitting with FoV (solver = SVD)** 13. Frontend sends: **FITTING_REQUEST** with solver = SVD 14. Backend returns: **FITTING_RESPONSE** (``FittingResponse``) :red-text:`Check 6:` the FITTING_RESPONSE should match Case 2-1 results **Case 3: Image fitting creating model image** 15. Frontend sends: **FITTING_REQUEST** (``FittingRequest``) .. code-block:: protobuf file_id = 0 create_model_image = true create_residual_image = false fixed_params = [false, false, false, false, false, false, true] fov_info = null region_id = -1 initial_values = [{amp: 10, center: {x: 320, y: 400}, fwhm: {x: 100, y: 50}, pa: 135}] 16. Backend returns: **FITTING_RESPONSE** and **REGION_HISTOGRAM_DATA** (for model image) :red-text:`Check 7:` the FITTING_RESPONSE should satisfy: - FITTING_RESPONSE.success = True - REGION_HISTOGRAM_DATA.fileId = 1 (model image) 17. Frontend sends: **ADD_REQUIRED_TILES** for model image (file_id = 1) 18. Backend returns: **RASTER_TILE_DATA** for model image :red-text:`Check 8:` all RASTER_TILE_DATA should have fileId = 1 **Case 4: Image fitting creating model and residual images** 19. Frontend sends: **FITTING_REQUEST** (``FittingRequest``) .. code-block:: protobuf file_id = 0 create_model_image = true create_residual_image = true region_id = -1 20. Backend returns: **FITTING_RESPONSE** and 2 **REGION_HISTOGRAM_DATA** (model + residual) :red-text:`Check 9:` the response should satisfy: - FITTING_RESPONSE.success = True - REGION_HISTOGRAM_DATA fileIds should contain 2 and 3 **Case 5: Image fitting with region, model and residual images** 21. Frontend sends: **SET_REGION** (``SetRegion``) .. code-block:: protobuf file_id = 0 region_id = 1 region_type = RECTANGLE control_points = [{x: 319.5, y: 399.5}, {x: 216.71, y: 200.0}] rotation = 0 22. Backend returns: **SET_REGION_ACK** (``SetRegionAck``) :red-text:`Check 10:` SET_REGION_ACK.success = True and regionId = 1 23. Frontend sends: **FITTING_REQUEST** (``FittingRequest``) .. code-block:: protobuf file_id = 0 create_model_image = true create_residual_image = true region_id = 1 solver = Cholesky offset = 0 24. Backend returns: **FITTING_RESPONSE** and 2 **REGION_HISTOGRAM_DATA** :red-text:`Check 11:` the FITTING_RESPONSE should satisfy: - FITTING_RESPONSE.success = True - REGION_HISTOGRAM_DATA fileIds should contain 4 and 5 **Case 6: Image fitting with background flux offset** 25. Frontend sends: **CLOSE_FILE** and opens a different file: .. code-block:: protobuf file = "M17_SWex-channel0-addOneGaussian-addBackgroundFlux.fits" file_id = 0 26. Frontend sends: **SET_REGION** and **FITTING_REQUEST** with offset = 9 27. Backend returns: **FITTING_RESPONSE** :red-text:`Check 12:` the FITTING_RESPONSE should satisfy: .. code-block:: protobuf success = true result_values[0].center = {x: 319.50, y: 399.50} result_values[0].amp = 10.00 offset_value = 10.00 log contains "Gaussian fitting with 1 component" - offsetValue and offsetError should be present and valid IMAGE_FITTING_CASA ~~~~~~~~~~~~~~~~~~ See the `source code `__. This test is identical in structure to IMAGE_FITTING_FITS, but uses CASA image files (.image) instead of FITS files. It verifies that Gaussian image fitting produces the same results regardless of file format. 1. Frontend sends: **OPEN_FILE** (``OpenFile``) .. code-block:: protobuf directory = "set_QA" file = "M17_SWex-channel0-addOneGaussian.image" file_id = 0 render_mode = RASTER 2. Backend returns: **OPEN_FILE_ACK** (``OpenFileAck``) and **REGION_HISTOGRAM_DATA** :red-text:`Check 1:` the OPEN_FILE_ACK should satisfy: - OPEN_FILE_ACK.success = True 3-27. Same workflow as IMAGE_FITTING_FITS (Cases 1-6), using ``.image`` files :red-text:`Checks 2-12:` same assertions as IMAGE_FITTING_FITS IMAGE_FITTING_HDF5 ~~~~~~~~~~~~~~~~~~ See the `source code `__. This test is identical in structure to IMAGE_FITTING_FITS, but uses HDF5 image files (.hdf5) instead of FITS files. It verifies that Gaussian image fitting produces the same results regardless of file format. 1. Frontend sends: **OPEN_FILE** (``OpenFile``) .. code-block:: protobuf directory = "set_QA" file = "M17_SWex-channel0-addOneGaussian.hdf5" file_id = 0 render_mode = RASTER 2. Backend returns: **OPEN_FILE_ACK** (``OpenFileAck``) and **REGION_HISTOGRAM_DATA** :red-text:`Check 1:` the OPEN_FILE_ACK should satisfy: - OPEN_FILE_ACK.success = True 3-27. Same workflow as IMAGE_FITTING_FITS (Cases 1-6), using ``.hdf5`` files :red-text:`Checks 2-12:` same assertions as IMAGE_FITTING_FITS IMAGE_FITTING_BAD ~~~~~~~~~~~~~~~~~ See the `source code `__. This test verifies error handling in image fitting, including cases where the solver exceeds the maximum number of iterations and where the fit does not converge. 1. Frontend sends: **CLOSE_FILE** (``CloseFile``) .. code-block:: protobuf file_id = -1 2. Frontend sends: **OPEN_FILE** (``OpenFile``) .. code-block:: protobuf directory = "set_QA" file = "ThreeComponent-inclined-2d-gaussian.fits" hdu = "0" file_id = 0 render_mode = RASTER 3. Backend returns: **OPEN_FILE_ACK** (``OpenFileAck``) and **REGION_HISTOGRAM_DATA** :red-text:`Check 1:` the OPEN_FILE_ACK should satisfy: - OPEN_FILE_ACK.success = True 4. Frontend sends: **ADD_REQUIRED_TILES** and **SET_CURSOR** 5. Backend returns: **RASTER_TILE_DATA** and **SPATIAL_PROFILE_DATA** **Case 1: Exceeded maximum number of iterations** 6. Frontend sends: **FITTING_REQUEST** (``FittingRequest``) with a 2-component Gaussian .. code-block:: protobuf file_id = 0 create_model_image = false create_residual_image = false fixed_params = [false, false, false, false, false, false, false, false, false, false, false, false, true] region_id = -1 initial_values = [{amp: 1, center: {x: 164, y: 280}, fwhm: {x: 100, y: 5}, pa: 270}, {amp: 1, center: {x: 220, y: 270}, fwhm: {x: 30, y: 100}, pa: 45}] solver = Cholesky offset = 0 7. Backend returns: **FITTING_RESPONSE** (``FittingResponse``) :red-text:`Check 2:` the FITTING_RESPONSE should satisfy: - FITTING_RESPONSE.log contains iteration exceeded message - FITTING_RESPONSE.message contains warning about exceeded iterations - Result values and errors should still be returned (platform-dependent) **Case 2: Fit did not converge** 8. Frontend sends: **FITTING_REQUEST** (``FittingRequest``) with intentionally bad initial values .. code-block:: protobuf file_id = 0 create_model_image = false create_residual_image = false fixed_params = [false, false, false, false, false, false, true] region_id = -1 initial_values = [{amp: 10, center: {x: 1000, y: 280}, fwhm: {x: 100, y: 5}, pa: 270}] solver = Cholesky offset = 0 9. Backend returns: **FITTING_RESPONSE** (``FittingResponse``) :red-text:`Check 3:` the FITTING_RESPONSE should satisfy: - The request should throw an error with a "did not converge" message IMAGE_FITTING_CANCEL ~~~~~~~~~~~~~~~~~~~~ See the `source code `__. This test verifies that an in-progress image fitting request can be cancelled via STOP_FITTING, and that a subsequent fitting request still succeeds. 1. Frontend sends: **CLOSE_FILE** (``CloseFile``) .. code-block:: protobuf file_id = -1 2. Frontend sends: **OPEN_FILE** (``OpenFile``) .. code-block:: protobuf directory = "set_QA" file = "M17_SWex-channel0-addOneGaussian.fits" hdu = "0" file_id = 0 render_mode = RASTER 3. Backend returns: **OPEN_FILE_ACK** (``OpenFileAck``) and **REGION_HISTOGRAM_DATA** :red-text:`Check 1:` the OPEN_FILE_ACK should satisfy: - OPEN_FILE_ACK.success = True 4. Frontend sends: **ADD_REQUIRED_TILES** and **SET_CURSOR** 5. Backend returns: **RASTER_TILE_DATA** and **SPATIAL_PROFILE_DATA** 6. Frontend sends: **SET_REGION** (``SetRegion``) .. code-block:: protobuf file_id = 0 region_id = 1 region_type = RECTANGLE control_points = [{x: 324, y: 398}, {x: 270, y: 270}] rotation = 0 :red-text:`Check 2:` SET_REGION_ACK.success = True and regionId = 1 **Step 1: Request fitting, then cancel** 7. Frontend sends: **FITTING_REQUEST** (``FittingRequest``) .. code-block:: protobuf file_id = 0 create_model_image = false create_residual_image = false fixed_params = [false, false, false, false, false, false, true] region_id = 1 initial_values = [{amp: 10, center: {x: 320, y: 400}, fwhm: {x: 100, y: 50}, pa: 135}] solver = Cholesky offset = 0 8. Backend streams: **FITTING_PROGRESS** (``FittingProgress``) 9. After receiving 10 progress updates, Frontend sends: **STOP_FITTING** (``StopFitting``) .. code-block:: protobuf file_id = 0 :red-text:`Check 3:` the FITTING_RESPONSE error should contain "task cancelled" **Step 2: Request fitting again and let it complete** 10. Frontend sends: **FITTING_REQUEST** (same as step 7) 11. Backend returns: **FITTING_RESPONSE** (``FittingResponse``) :red-text:`Check 4:` the FITTING_RESPONSE should satisfy: .. code-block:: protobuf success = true result_values[0].center = {x: 319.50, y: 399.50} result_values[0].amp = 10.00 result_values[0].fwhm = {x: 170.64, y: 41.48} result_values[0].pa = 142.16 log contains "Gaussian fitting with 1 component" - All resultErrors should be close to zero