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

Operator RandomObjectBBox #2657

Merged
merged 15 commits into from
Feb 18, 2021
Merged

Operator RandomObjectBBox #2657

merged 15 commits into from
Feb 18, 2021

Conversation

mzient
Copy link
Contributor

@mzient mzient commented Feb 4, 2021

Why we need this PR?

Pick one, remove the rest

  • It adds new feature needed for UNet: select bounding box of a random blob from randomly selected foreground class

What happened in this PR?

Fill relevant points, put NA otherwise. Replace anything inside []

  • What solution was applied:
    • Check what classes are present
    • Select random class according to weights (if given)
    • Filter the input
    • Find blobs
    • Find boxes
    • Apply threshold and k-largest - if no good blobs found, try other classes
    • If no class is good, return entire input area
  • Affected modules and functionalities:
    • LabelBBox - bugfixes
  • Key points relevant for the review:
    • Argument handling
  • Validation and testing:
    • Python tests
  • Documentation (including examples):
    • Docstrings

JIRA TASK: DALI-1817

@mzient mzient requested a review from a team February 4, 2021 23:06
@mzient mzient changed the title [WIP] Operator RandomObjectBBox Operator RandomObjectBBox Feb 8, 2021
@mzient
Copy link
Contributor Author

mzient commented Feb 8, 2021

!build

@dali-automaton
Copy link
Collaborator

CI MESSAGE: [2056699]: BUILD STARTED

@lgtm-com
Copy link
Contributor

lgtm-com bot commented Feb 8, 2021

This pull request introduces 1 alert when merging 5402c7dad5d57cca5a0cf3e47658c4aae626682f into 2c0b597 - view on LGTM.com

new alerts:

  • 1 for Unreachable code

@dali-automaton
Copy link
Collaborator

CI MESSAGE: [2056699]: BUILD FAILED

@mzient
Copy link
Contributor Author

mzient commented Feb 9, 2021

!build

@dali-automaton
Copy link
Collaborator

CI MESSAGE: [2058334]: BUILD STARTED

@dali-automaton
Copy link
Collaborator

CI MESSAGE: [2058334]: BUILD FAILED

@mzient
Copy link
Contributor Author

mzient commented Feb 9, 2021

!build

@dali-automaton
Copy link
Collaborator

CI MESSAGE: [2058874]: BUILD STARTED

@dali-automaton
Copy link
Collaborator

CI MESSAGE: [2058877]: BUILD STARTED

@dali-automaton
Copy link
Collaborator

CI MESSAGE: [2058877]: BUILD PASSED

@dali-automaton
Copy link
Collaborator

CI MESSAGE: [2058874]: BUILD PASSED

context.classes.push_back(cls);
context.weights.push_back(1);
}
std::sort(context.classes.begin(), context.classes.end());
Copy link
Contributor Author

@mzient mzient Feb 9, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorting is necessary, because the order of elements in unordered set is not only undefined with respect to current contents, but also depends on previous contents (before clear) - as this changes the number of bins.

It's cheaper to copy it from unordered set and sort, because unordered set has better memory management than set and we need a copy to a flat array anyway.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would be for writing such comments directly in the code instead of the PR review.

This operator takes a labeled segmentation map as its input. With probability ``foreground_prob``
it randomly selects a label (uniformly or according to the distribution given as ``class_weights``),
extracts connected blobs of pixels with the selected label and randomly selects one of them
(with additional constraints given as ``k_largest`` and ``threshold``).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel the content of this parenthesis deserves its own sentence. It is not clear enough now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll try to rephrase it.

it randomly selects a label (uniformly or according to the distribution given as ``class_weights``),
extracts connected blobs of pixels with the selected label and randomly selects one of them
(with additional constraints given as ``k_largest`` and ``threshold``).
With probability 1-foreground_prob, entire area of the input is returned.)")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
With probability 1-foreground_prob, entire area of the input is returned.)")
With probability 1-foreground_prob, the entire area of the input is returned.)")

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe also add info of what are the outputs (and possible formats?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if it's wort repeating here.... I'll take a look.

return 1 + separate_corners + output_class;
})
.AddOptionalArg("ignore_class", R"(If True, all objects are picked with equal probability,
regardless of the class they belong to. Otherwise, a class is picked first and then object is
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
regardless of the class they belong to. Otherwise, a class is picked first and then object is
regardless of the class they belong to. Otherwise, a class is picked first and then the object is

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

an object

This flag only affects the probability with which blobs are selected. It does not cause
blobs of different classes to be merged.)", false)
.AddOptionalArg("output_class", R"(If True, an additional output is produced which contains the
label of the class to which the resulting box belongs, or background label if foreground box
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
label of the class to which the resulting box belongs, or background label if foreground box
label of the class to which the resulting box belongs, or the background label if the selected box

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

true that

void RandomObjectBBox::GetClassesAndWeightsArgs(
ClassVec &classes, WeightVec &weights, int &background, int sample_idx) {
background = background_[sample_idx].data[0];
if (ignore_class_)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rather put this if before calling this function.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But then I'd have to get background in the calling function.

Copy link
Contributor

@jantonguirao jantonguirao Feb 11, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, or have a separate getter for the background so that you see:

GetBackground(...)
if (!ignore_label_)
  GetClassesAndWeights(...)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't work like this. If there are classes or weights, background may depend on them.

void StoreBox(const OutListCPU<int, 1> &out1,
const OutListCPU<int, 1> &out2,
RandomObjectBBox::OutputFormat format,
int sample_idx, Box &&box) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a question: Have you considered using one of the existing structures for Box/ROI/etc?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These structures have fixed number of dimensions whereas this works with dynamic dimensions.

}
if (choose_different_background) {
// This will yield incorrect result if the set of classes contains both INT_MIN and INT_MAX,
// which is a sacrifice I am willing to make.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you enforce somewhere that this is not the case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could even loop over and decrement until I find a free slot.... I don't know how much value is there in it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking of a simple assert or DALI_ENFORCE.

.AddOptionalArg<vector<int>>("threshold", R"(Minimum extent(s) of the bounding boxes to return.

If current class doesn't contain any bounding box that meets this condition, the largest one
is returned.)", nullptr, true)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm... I see that I didn't implement that (and just try a different class or fall back to background).
I don't know what I should do in this case. Change implementation or change the docs? Arguably, it makes sense not to return a box below the threshold, even if it's the only one in its class.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am OK with that (not returning the box). Given that it is properly documented

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now the documentation follows what happens in the code.

SmallVector<std::pair<int64_t, int>, 32> vol_idx;
vol_idx.resize(n);
for (int i = 0; i < n; i++) {
vol_idx[i] = { -volume(boxes[i]), i };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could leave the volume possitive and use std::greater<> with std::sort

Copy link
Contributor Author

@mzient mzient Feb 15, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But then I'd have to reverse sign of i to get stability.

if (n <= 0)
return -1;

if (k_largest_ > 0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small improvement: If your k_largest is bigger than the actual number of objects you don't need to sort either.


VALUE_SWITCH(ndim, static_ndim, (1, 2, 3, 4, 5, 6),
(
auto *box_data = reinterpret_cast<Box<static_ndim, int>*>(ctx.box_data.data());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wasn't it technically undefined behavior? :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know, but it's sort-of less undefined here, since it crosses function boundary and it's a tad safer to type-pun an array instead of one object.

template <typename T>
bool RandomObjectBBox::PickForegroundBox(
SampleContext &context, const InTensorCPU<T> &input) {
GetClassesAndWeightsArgs(context.classes, context.weights, context.background,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd move this call inside if (ignore_class_) and remove the check inside GetClassesAndWeights? Maybe have a separate getter for the background

}
std::sort(context.classes.begin(), context.classes.end());
} else {
for (int i = 0; i < static_cast<int>(context.classes.size()); i++) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
for (int i = 0; i < static_cast<int>(context.classes.size()); i++) {
for (size_t i = 0; i < context.classes.size(); i++) {

class_label_out = view<int, 0>(ws.OutputRef<CPUBackend>(class_output_idx_));

TensorShape<> default_anchor;
default_anchor.resize(ndim);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
default_anchor.resize(ndim);
default_anchor.resize(ndim, 0);


if (HasClassLabelOutput())
class_label_out.data[i][0] = ctx.class_label;
}, volume(in_shape.tensor_shape_span(i)));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
}, volume(in_shape.tensor_shape_span(i)));
}, in_shape.tensor_size(i));


bool output_class_ = spec.GetArgument<bool>("output_class");

if (ignore_class_ && (classes_.IsDefined() || weights_.IsDefined() || output_class_)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, so if you have this, you can simply place an assert(!ignore_class) in the function where you get classes and weights

Copy link
Contributor Author

@mzient mzient Feb 11, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then I'd have to duplicate the code to get background value - and the control flow doesn't favor it.


format = formats[fmt]
fmt = (fmt + 1) % len(formats)
dtype = types[np.random.randint(0, len(types))]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use np.random.choice(types)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, now numpy complain that random choice is deprecated for non-numeric types... I'd leave it as-is and fix it in the parallel PR...?


format = format or "anchor_shape"

for _ in range(50):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you make the number of iterations a parameter of the function?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could, but what for?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's easy to change later if we need to

@mzient
Copy link
Contributor Author

mzient commented Feb 11, 2021

!build

@dali-automaton
Copy link
Collaborator

CI MESSAGE: [2067103]: BUILD STARTED

@dali-automaton
Copy link
Collaborator

CI MESSAGE: [2067103]: BUILD PASSED

@mzient mzient force-pushed the RandomObjectBBoxOp branch 2 times, most recently from 49fe8d8 to 2ecd968 Compare February 12, 2021 09:28
@mzient
Copy link
Contributor Author

mzient commented Feb 12, 2021

!build

@dali-automaton
Copy link
Collaborator

CI MESSAGE: [2070401]: BUILD STARTED

@dali-automaton
Copy link
Collaborator

CI MESSAGE: [2070401]: BUILD PASSED

Signed-off-by: Michał Zientkiewicz <[email protected]>
Signed-off-by: Michał Zientkiewicz <[email protected]>
Signed-off-by: Michał Zientkiewicz <[email protected]>
Signed-off-by: Michał Zientkiewicz <[email protected]>
Signed-off-by: Michał Zientkiewicz <[email protected]>
 * Propagate batch size from batch external sources to per-sample
external sources
 * Fix bugs

Signed-off-by: Michał Zientkiewicz <[email protected]>
 * Add `first = False` in external source handling to produce an error
   when external sources disagree on batch size.

Signed-off-by: Michał Zientkiewicz <[email protected]>
Signed-off-by: Michał Zientkiewicz <[email protected]>
Signed-off-by: Michał Zientkiewicz <[email protected]>
Signed-off-by: Michał Zientkiewicz <[email protected]>
Signed-off-by: Michał Zientkiewicz <[email protected]>
@mzient
Copy link
Contributor Author

mzient commented Feb 15, 2021

!build

@dali-automaton
Copy link
Collaborator

CI MESSAGE: [2077424]: BUILD STARTED

@dali-automaton
Copy link
Collaborator

CI MESSAGE: [2077424]: BUILD PASSED

@mzient
Copy link
Contributor Author

mzient commented Feb 16, 2021

!build

@dali-automaton
Copy link
Collaborator

CI MESSAGE: [2078838]: BUILD STARTED

@dali-automaton
Copy link
Collaborator

CI MESSAGE: [2078838]: BUILD PASSED

Copy link
Contributor

@klecki klecki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm yet to review the tests, but I'm already done with most of the implementation so posting my comments.
The flow looks correct but I had objections to the SampleContext and its API: mainly the fact that the Init will leave it in some weird state and the GetBgFgAndWeights is done a bit from the outside (even though it probably shouldn't access the ArgValues directly, it would make more sense to me if the initialization was done first and left the object in a complete state).

format_ = ParseOutputFormat(spec.GetArgument<string>("format"));
ignore_class_ = spec.GetArgument<bool>("ignore_class");

bool output_class_ = spec.GetArgument<bool>("output_class");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May be misleading someone into thinking that it is a member variable.

Suggested change
bool output_class_ = spec.GetArgument<bool>("output_class");
bool output_class = spec.GetArgument<bool>("output_class");

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will fix.

StoreBox(out1, out2, format_, i, default_anchor, input.tensor_shape(i));
if (HasClassLabelOutput()) {
SampleContext &ctx = contexts_[0];
GetBgFgAndWeights(ctx.classes, ctx.weights, ctx.background, i);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't you need to just output the background for this sample. Is the whole GetBgFgAndWeights necessary here? There won't be any further processing done for this sample, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Background label may be automatically assigned so as to skip classes, so I need to process them anyway.

StoreBox(out1, out2, format, sample_idx, box.lo, box.hi);
}

void RandomObjectBBox::GetBgFgAndWeights(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I'm reading this I feel a bit of spaghetti. The context has Init, but it only resizes some data, than there is this function, which directly modifies context's members (but if ignore_class_ is true I guess it will just leave them in an undefined state, either some leftovers from previous sample or just empty), than the context again has methods to operate on for example weights, assuming that they come from outside and are valid (and it uses them to further initialize some members).

I'm thinking how to untangle this, maybe I would start with zeroing the classes and weights in the context Init or having the Init inspect the classes and weights for that input and initialize those (so do more of GetBgFgAndWeights).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I admit that it is indeed spaghettish, but I'd prefer to address this in the followup (parallelization PR), as changing this logic here would result in very painful rebasing there - the context object has undergone significant changes between the two.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can understand that, if you can take of untangling it a bit in the follow up I would be ok with that under current circumstances.

}

if (HasClassLabelOutput())
class_label_out.data[i][0] = ctx.class_label;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This result passed via ctx from the two possible branches of the if (PickForegroundBox(ctx)) feels a bit weird.
Maybe encapsulate it in StoreLabel(i, ctx.selected_label)/StoreLabel(i, ctx.background) in the if/else respectively.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think makes much sense to encapsulate a simple assignment in a function - especially given that I'd either have to pass (ugly) or store in the object (uglier) the class_label_out variable, which is now local.


context.labels.erase(context.background);

if (!classes_.IsDefined() && !weights_.IsDefined()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the last case, for the GetBgFgAndWeights, but it's not handled there. I understand that you need to gather the labels first, but than, why not gather them in the Init? You're already inspecting the input a bit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Init is called in the main thread and I definitely don't want to scan entire input there.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the fg case (else), where you add the tasks to thread pool it's already in the lambda and I don't see any other invocation.

Copy link
Contributor Author

@mzient mzient Feb 17, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed. However, I don't need to call init at all for the background case - I don't need the temporary storage and allocating 100s of megabytes of arrays that would be never used is not something I'd do lightly.

context.classes.clear();
context.weights.clear();
for (auto cls : context.labels) {
context.classes.push_back(cls);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The above comment is because this looked really familiar to 230-231.

GetLabelBoundingBoxes(boxes, ctx.blobs.to_static<static_ndim>(), -1);
int box_idx = PickBox(boxes, ctx.sample_idx);
if (box_idx >= 0) {
ctx.SelectBox(box_idx);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you already did the reinterpret why not:

Suggested change
ctx.SelectBox(box_idx);
ctx.SelectBox(boxes[box_idx]);

assert(threshold_.get().shape[sample_idx] == TensorShape<1>{ ndim });
for (int i = 0; i < ndim; i++)
threshold[i] = thresh[i];
end = std::remove_if(beg, end, [threshold](auto box) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any difference if the auto box was a reference instead of pass by value?

Copy link
Contributor Author

@mzient mzient Feb 17, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It probably should be.

Copy link
Contributor

@klecki klecki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I focused mostly on the test coverage (and didn't dig that deep into generating the test data and results in python), so mostly suggestions for some more testing that could be done.


def test_num_output():
"""Test that a proper number of outputs is produced, depending on arguments"""
inp = fn.external_source(data, batch=False)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was looking where the data comes from, and it surprised me below. Could you maybe move it to the top?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure.

threshold_opt = [None, 3, list(range(1, 1+ndim)), fn.random.uniform(range=(1,5), shape=[ndim], dtype=dali.types.INT32, seed=13231)]
threshold = np.random.choice(threshold_opt);
k_largest_opt = [None, 1, 2, 5]
k_largest = np.random.choice(k_largest_opt);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if having threshold and k_largest as randomly tested is a good decision, probably it would be safer to make sure that they are tested with both unspecified, one of them specified and both specified.

To not have have cartesian product of all possibilities, you could probably test that for a praticular setting of classes etc.


return label

def batch_generator(batch_size, ndim, dtype):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to test with data that would give deterministic boxes as a result, for example have classes with 1 box, and set the weight for one of them to 1 and rest to 0?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's requesting 100s extra lines of tests for something that's pretty well covered by existing tests.

from test_utils import check_batch
from test_utils import np_type_to_dali
from nose.tools import raises, assert_raises, nottest

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would maybe consider adding negative tests, stuff like not matching number of weights and classes passed should raise, etc. and if the ignore_classes is specified other class-related arguments should cause an error.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can try.

Signed-off-by: Michał Zientkiewicz <[email protected]>
Signed-off-by: Michał Zientkiewicz <[email protected]>
@dali-automaton
Copy link
Collaborator

CI MESSAGE: [2083639]: BUILD STARTED

@dali-automaton
Copy link
Collaborator

CI MESSAGE: [2083639]: BUILD PASSED

@mzient mzient merged commit 665d9ab into NVIDIA:master Feb 18, 2021
@JanuszL JanuszL mentioned this pull request May 19, 2021
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

Successfully merging this pull request may close these issues.

5 participants