Proper way to clone an ImageWindow

AstroFabry

New member
Jul 4, 2020
2
1
Hi guys.

I'm trying to find a proper way to clone an ImageWindow but I'm stuck with FITS keywords that do not want to get copied ...
Here is my code (partially copied from other scripts and partially modified):
JavaScript:
this.cloneProperties = function (oldView, newView) {
        try {
            console.warningln("Cloning properties from " + oldView.id + " to " + newView.id);
            let oldProperties = [];
            for (let idx = 0; idx < oldView.properties.length; ++idx) {
                oldProperties.push([oldView.properties[idx], oldView.propertyValue(oldView.properties[idx]), oldView.propertyAttributes((oldView.properties[idx]))]);
            }
            newView.beginProcess();
            for (let idx = 0; idx < oldProperties.length; ++idx) {
                try {
                    newView.setPropertyValue(oldProperties[idx][0], oldProperties[idx][1]);
                    newView.setPropertyAttributes(oldProperties[idx][0], oldProperties[idx][2]);
                } catch (e) {
                    this.logger.error(e.message);
                }
            }
// THIS PART DOESN'T WORK - BEGIN
            for (let idx = 0; idx < oldView.window.keywords.length; ++idx)
            {
                newView.window.keywords.push(new FITSKeyword(oldView.window.keywords[idx]));
            }
// THIS PART DOESN'T WORK - END
            newView.endProcess();
        } catch (e) {
            this.logger.error(e.message);
        };
    };

    this.cloneView = function (view, newId) {
        try {
            let newWindow = new ImageWindow(1, 1, 1, view.window.bitsPerSample, view.window.isFloatSample, view.image.colorSpace != ColorSpace_Gray, newId);
            newWindow.mainView.beginProcess(UndoFlag_NoSwapFile);
            newWindow.mainView.image.assign(view.image);
            newWindow.mainView.endProcess();
            this.cloneProperties(view, newWindow.mainView);
            newWindow.mainView.stf = view.stf;
            newWindow.show();
            newWindow.zoomToFit();
            return newWindow.mainView;
        } catch (e) {
            this.logger.error(e.message);
        };
        return null;
    };
Does anyone know how to do it properly ?

Thank you very much for your help !

AstroFabry
 
Last edited:
  • Like
Reactions: pifrk

Juan Conejero

PixInsight Staff
Sep 2, 2004
8,380
467
57
Valencia, Spain
pixinsight.com
This happens because the following property:

Array ImageWindow.keywords

provides an Array of FITSKeyword objects that is disconnected from the ImageWindow object. When you invoke this property, a new Array is created dynamically with the FITS keywords of the image, but it is an independent, isolated object. So when you call the Array.push() method for this object, what you are pushing is not being pushed to the actual array of keywords in the image window.

This happens with all properties of native objects. Native objects don't have a JavaScript implementation, but a C++ implementation in the core PixInsight application. Properties of native objects are not actual JavaScript properties, but copies not connected with their parent objects. Properties with scalar values, such as:

int ImageWindow.zoomFactor

don't pose any practical problems in general, since you can read and write them directly. However, properties with structured values, such as:

Point ImageWindow.position

don't provide direct access to the actual properties of their parent objects. The practical consequence of this is that you are forced to read the property and store it in a local variable, alter the variable if needed, and then copy the variable back to the object's property.

The correct implementation to modify the FITS keywords of an existing image window is as follows:

JavaScript:
let keywords = oldView.window.keywords;
// ... do something here with the keywords array, if required ...
newView.window.keywords = keywords;
I am conscious that this behavior is pretty counterintuitive. However, once one understands how the internals of a scripting language embedding work, one applies these concepts instinctively after acquiring some practice.

Admittedly, when I first implemented most of PJSR's core objects I made some design mistakes. One of them is being clearly exposed here. For example, if I were to implement these core objects now, ImageWindow wouldn't have a keywords property but a pair of getter/setter methods:

Array ImageWindow.keywords()
void ImageWindow.setKeywords( Array )

Obviously, now it's way too late to do this, so we have to live with what we have. At least for now :)
 

AstroFabry

New member
Jul 4, 2020
2
1
The correct implementation to modify the FITS keywords of an existing image window is as follows:

JavaScript:
let keywords = oldView.window.keywords;
// ... do something here with the keywords array, if required ...
newView.window.keywords = keywords;
Thank you @Juan Conejero
I implemented it the way you suggested and, of course, it worked.
I thought about that solution in the first place but I discarded it right away because I was afraid that if the keywords of the original image were to be modified after the cloning, those modifications would have been propagated into the cloned image as well and this, of course, was not acceptable since the cloning is a little bit like a fork operation: two things identical up to the point of the cloning that start their own independent life after that. I thought about this problem because the copy you suggested for the arrays are indeed shallow copies (i.e. newView.window.keywords is just an alias of oldView.window.keywords). Nonetheless, if the C++ objects and Javascript objects are in fact different then this should not be a problem. I am right?